Merge "Add the theme for the bottom sheet UI" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ee7ace..9ee74e3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,10 +19,12 @@
     ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.wearable.flags-aconfig-java{.generated_srcjars}",
     ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
     ":android.chre.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+    ":android.companion.virtualdevice.flags-aconfig-java{.generated_srcjars}",
     ":android.content.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
     ":android.content.res.flags-aconfig-java{.generated_srcjars}",
@@ -34,6 +36,7 @@
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
     ":android.location.flags-aconfig-java{.generated_srcjars}",
+    ":android.media.codec-aconfig-java{.generated_srcjars}",
     ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
     ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
     ":android.net.platform.flags-aconfig-java{.generated_srcjars}",
@@ -67,6 +70,7 @@
     ":com.android.hardware.input-aconfig-java{.generated_srcjars}",
     ":com.android.input.flags-aconfig-java{.generated_srcjars}",
     ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.internal.pm.pkg.component.flags-aconfig-java{.generated_srcjars}",
     ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
     ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
     ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
@@ -1123,3 +1127,35 @@
     ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Wearable Sensing
+aconfig_declarations {
+    name: "android.app.wearable.flags-aconfig",
+    package: "android.app.wearable",
+    srcs: ["core/java/android/app/wearable/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.wearable.flags-aconfig-java",
+    aconfig_declarations: "android.app.wearable.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+aconfig_declarations {
+    name: "com.android.internal.pm.pkg.component.flags-aconfig",
+    package: "com.android.internal.pm.pkg.component.flags",
+    srcs: ["core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "com.android.internal.pm.pkg.component.flags-aconfig-java",
+    aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+    name: "com.android.internal.pm.pkg.component.flags-aconfig-java-host",
+    aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS
new file mode 100644
index 0000000..89ce5140
--- /dev/null
+++ b/MEMORY_OWNERS
@@ -0,0 +1,6 @@
+surenb@google.com
+tjmercier@google.com
+kaleshsingh@google.com
+jyescas@google.com
+carlosgalo@google.com
+jji@google.com
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 5d65d9d..8a5206f 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,20 @@
 package: "com.android.server.job"
 
 flag {
+    name: "batch_active_bucket_jobs"
+    namespace: "backstage_power"
+    description: "Include jobs in the ACTIVE bucket in the job batching effort. Don't let them run as freely as they're ready."
+    bug: "299329948"
+}
+
+flag {
+    name: "batch_connectivity_jobs_per_network"
+    namespace: "backstage_power"
+    description: "Have JobScheduler attempt to delay the start of some connectivity jobs until there are several ready or the network is active"
+    bug: "28382445"
+}
+
+flag {
     name: "do_not_force_rush_execution_at_boot"
     namespace: "backstage_power"
     description: "Don't force rush job execution right after boot completion"
@@ -20,10 +34,3 @@
     description: "Throw an exception if an unsupported app uses JobInfo.setBias"
     bug: "300477393"
 }
-
-flag {
-    name: "batch_jobs_on_network_activation"
-    namespace: "backstage_power"
-    description: "Have JobScheduler attempt to delay the start of some connectivity jobs until the network is actually active"
-    bug: "318394184"
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 6550f26..012ede2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -96,7 +96,6 @@
 
     static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
     private static final String KEY_CONCURRENCY_LIMIT = CONFIG_KEY_PREFIX_CONCURRENCY + "limit";
-    @VisibleForTesting
     static final int DEFAULT_CONCURRENCY_LIMIT;
 
     static {
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 57467e3..cea16d6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -65,6 +65,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ServiceInfo;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
@@ -89,6 +90,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -538,7 +540,9 @@
                                 apiQuotaScheduleUpdated = true;
                             }
                             break;
+                        case Constants.KEY_MIN_READY_CPU_ONLY_JOBS_COUNT:
                         case Constants.KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT:
+                        case Constants.KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS:
                         case Constants.KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS:
                             mConstants.updateBatchingConstantsLocked();
                             break;
@@ -554,6 +558,8 @@
                         case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
                         case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
                         case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
+                        case Constants.KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS:
+                        case Constants.KEY_CONN_TRANSPORT_BATCH_THRESHOLD:
                         case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
                         case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
                             mConstants.updateConnectivityConstantsLocked();
@@ -602,6 +608,8 @@
                     sc.onConstantsUpdatedLocked();
                 }
             }
+
+            mHandler.sendEmptyMessage(MSG_CHECK_JOB);
         }
 
         @Override
@@ -646,8 +654,12 @@
      */
     public static class Constants {
         // Key names stored in the settings value.
+        private static final String KEY_MIN_READY_CPU_ONLY_JOBS_COUNT =
+                "min_ready_cpu_only_jobs_count";
         private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
                 "min_ready_non_active_jobs_count";
+        private static final String KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS =
+                "max_cpu_only_job_batch_delay_ms";
         private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
                 "max_non_active_job_batch_delay_ms";
         private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
@@ -665,6 +677,10 @@
                 "conn_update_all_jobs_min_interval_ms";
         private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
                 "conn_low_signal_strength_relax_frac";
+        private static final String KEY_CONN_TRANSPORT_BATCH_THRESHOLD =
+                "conn_transport_batch_threshold";
+        private static final String KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+                "conn_max_connectivity_job_batch_delay_ms";
         private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
                 "prefetch_force_batch_relax_threshold_ms";
         // This has been enabled for 3+ full releases. We're unlikely to disable it.
@@ -713,7 +729,11 @@
         private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS =
                 "max_num_persisted_job_work_items";
 
-        private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
+        private static final int DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT =
+                Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+        private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT =
+                Math.min(5, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+        private static final long DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
         private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
@@ -725,6 +745,15 @@
         private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
         private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
         private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
+        private static final SparseIntArray DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD =
+                new SparseIntArray();
+        private static final long DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+                31 * MINUTE_IN_MILLIS;
+        static {
+            DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.put(
+                    NetworkCapabilities.TRANSPORT_CELLULAR,
+                    Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3));
+        }
         private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
         private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
         private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -762,11 +791,23 @@
         static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
 
         /**
-         * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
+         * Minimum # of jobs that have to be ready for JS to be happy running work.
+         * Only valid if {@link Flags#batchActiveBucketJobs()} is true.
+         */
+        int MIN_READY_CPU_ONLY_JOBS_COUNT = DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT;
+
+        /**
+         * Minimum # of non-ACTIVE jobs that have to be ready for JS to be happy running work.
          */
         int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT;
 
         /**
+         * Don't batch a CPU-only job if it's been delayed due to force batching attempts for
+         * at least this amount of time.
+         */
+        long MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+
+        /**
          * Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for
          * at least this amount of time.
          */
@@ -822,6 +863,17 @@
          */
         public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
                 DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
+        /**
+         * The minimum batch requirement per each transport type before allowing a network to run
+         * on a network with that transport.
+         */
+        public SparseIntArray CONN_TRANSPORT_BATCH_THRESHOLD = new SparseIntArray();
+        /**
+         * Don't batch a connectivity job if it's been delayed due to force batching attempts for
+         * at least this amount of time.
+         */
+        public long CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+                DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
 
         /**
          * The amount of time within which we would consider the app to be launching relatively soon
@@ -972,11 +1024,31 @@
         public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER
                 && EconomyManager.DEFAULT_ENABLE_TARE_MODE == EconomyManager.ENABLED_MODE_ON;
 
+        public Constants() {
+            copyTransportBatchThresholdDefaults();
+        }
+
         private void updateBatchingConstantsLocked() {
-            MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
+            // The threshold should be in the range
+            // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+            MIN_READY_CPU_ONLY_JOBS_COUNT =
+                    Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+                            DeviceConfig.getInt(
+                                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                                    KEY_MIN_READY_CPU_ONLY_JOBS_COUNT,
+                                    DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT)));
+            // The threshold should be in the range
+            // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+            MIN_READY_NON_ACTIVE_JOBS_COUNT =
+                    Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+                            DeviceConfig.getInt(
+                                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                                    KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
+                                    DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT)));
+            MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
-                    DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
+                    KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+                    DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS);
             MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
@@ -1024,6 +1096,46 @@
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
                     DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
+            final String batchThresholdConfigString = DeviceConfig.getString(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_TRANSPORT_BATCH_THRESHOLD,
+                    null);
+            final KeyValueListParser parser = new KeyValueListParser(',');
+            CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+            try {
+                parser.setString(batchThresholdConfigString);
+
+                for (int t = parser.size() - 1; t >= 0; --t) {
+                    final String transportString = parser.keyAt(t);
+                    try {
+                        final int transport = Integer.parseInt(transportString);
+                                // The threshold should be in the range
+                                // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+                        CONN_TRANSPORT_BATCH_THRESHOLD.put(transport, Math.max(0,
+                                Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+                                        parser.getInt(transportString, 1))));
+                    } catch (NumberFormatException e) {
+                        Slog.e(TAG, "Bad transport string", e);
+                    }
+                }
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Bad string for " + KEY_CONN_TRANSPORT_BATCH_THRESHOLD, e);
+                // Use the defaults.
+                copyTransportBatchThresholdDefaults();
+            }
+            CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = Math.max(0, Math.min(24 * HOUR_IN_MILLIS,
+                    DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+                    DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS)));
+        }
+
+        private void copyTransportBatchThresholdDefaults() {
+            for (int i = DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.size() - 1; i >= 0; --i) {
+                CONN_TRANSPORT_BATCH_THRESHOLD.put(
+                        DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.keyAt(i),
+                        DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.valueAt(i));
+            }
         }
 
         private void updatePersistingConstantsLocked() {
@@ -1168,8 +1280,11 @@
         void dump(IndentingPrintWriter pw) {
             pw.println("Settings:");
             pw.increaseIndent();
+            pw.print(KEY_MIN_READY_CPU_ONLY_JOBS_COUNT, MIN_READY_CPU_ONLY_JOBS_COUNT).println();
             pw.print(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
                     MIN_READY_NON_ACTIVE_JOBS_COUNT).println();
+            pw.print(KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+                    MAX_CPU_ONLY_JOB_BATCH_DELAY_MS).println();
             pw.print(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
                     MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println();
             pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
@@ -1185,6 +1300,10 @@
                     .println();
             pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
                     .println();
+            pw.print(KEY_CONN_TRANSPORT_BATCH_THRESHOLD, CONN_TRANSPORT_BATCH_THRESHOLD.toString())
+                    .println();
+            pw.print(KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+                            CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS).println();
             pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
                     PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
 
@@ -2835,9 +2954,9 @@
         mJobPackageTracker.notePending(job);
     }
 
-    void noteJobsPending(List<JobStatus> jobs) {
-        for (int i = jobs.size() - 1; i >= 0; i--) {
-            noteJobPending(jobs.get(i));
+    void noteJobsPending(ArraySet<JobStatus> jobs) {
+        for (int i = jobs.size() - 1; i >= 0; --i) {
+            noteJobPending(jobs.valueAt(i));
         }
     }
 
@@ -3463,7 +3582,7 @@
     }
 
     final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
-        final ArrayList<JobStatus> newReadyJobs = new ArrayList<>();
+        final ArraySet<JobStatus> newReadyJobs = new ArraySet<>();
 
         @Override
         public void accept(JobStatus job) {
@@ -3491,9 +3610,27 @@
      * policies on when we want to execute jobs.
      */
     final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
-        int forceBatchedCount;
-        int unbatchedCount;
+        /**
+         * Set of jobs that will be force batched, mapped by network. A {@code null} network is
+         * reserved/intended for CPU-only (non-networked) jobs.
+         * The set may include already running jobs.
+         */
+        @VisibleForTesting
+        final ArrayMap<Network, ArraySet<JobStatus>> mBatches = new ArrayMap<>();
+        /** List of all jobs that could run if allowed. Already running jobs are excluded. */
+        @VisibleForTesting
         final List<JobStatus> runnableJobs = new ArrayList<>();
+        /**
+         * Convenience holder of all jobs ready to run that won't be force batched.
+         * Already running jobs are excluded.
+         */
+        final ArraySet<JobStatus> mUnbatchedJobs = new ArraySet<>();
+        /**
+         * Count of jobs that won't be force batched, mapped by network. A {@code null} network is
+         * reserved/intended for CPU-only (non-networked) jobs.
+         * The set may include already running jobs.
+         */
+        final ArrayMap<Network, Integer> mUnbatchedJobCount = new ArrayMap<>();
 
         public MaybeReadyJobQueueFunctor() {
             reset();
@@ -3540,27 +3677,77 @@
                     shouldForceBatchJob = false;
                 } else {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
-                    final boolean batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
-                            && nowElapsed - job.getFirstForceBatchedTimeElapsed()
-                            >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
-                    shouldForceBatchJob =
-                            mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
-                                    && job.getEffectiveStandbyBucket() != ACTIVE_INDEX
-                                    && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
-                                    && !batchDelayExpired;
+                    final long timeUntilDeadlineMs = job.hasDeadlineConstraint()
+                            ? job.getLatestRunTimeElapsed() - nowElapsed
+                            : Long.MAX_VALUE;
+                    // Differentiate behavior based on whether the job needs network or not.
+                    if (Flags.batchConnectivityJobsPerNetwork()
+                            && job.hasConnectivityConstraint()) {
+                        // For connectivity jobs, let them run immediately if the network is already
+                        // active (in a state for job run), otherwise, only run them if there are
+                        // enough to meet the batching requirement or the job has been waiting
+                        // long enough.
+                        final boolean batchDelayExpired =
+                                job.getFirstForceBatchedTimeElapsed() > 0
+                                        && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+                                        >= mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+                        shouldForceBatchJob = !batchDelayExpired
+                                && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+                                && timeUntilDeadlineMs
+                                        > mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS / 2
+                                && !mConnectivityController.isNetworkInStateForJobRunLocked(job);
+                    } else {
+                        final boolean batchDelayExpired;
+                        final boolean batchingEnabled;
+                        if (Flags.batchActiveBucketJobs()) {
+                            batchingEnabled = mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT > 1
+                                    && timeUntilDeadlineMs
+                                            > mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS / 2
+                                    // Active UIDs' jobs were by default treated as in the ACTIVE
+                                    // bucket, so we must explicitly exclude them when batching
+                                    // ACTIVE jobs.
+                                    && !job.uidActive
+                                    && !job.getJob().isExemptedFromAppStandby();
+                            batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+                                    && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+                                            >= mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+                        } else {
+                            batchingEnabled = mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
+                                    && job.getEffectiveStandbyBucket() != ACTIVE_INDEX;
+                            batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+                                    && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+                                            >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
+                        }
+                        shouldForceBatchJob = batchingEnabled
+                                && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+                                && !batchDelayExpired;
+                    }
                 }
 
+                // If connectivity job batching isn't enabled, treat every job as
+                // a non-connectivity job since that mimics the old behavior.
+                final Network network =
+                        Flags.batchConnectivityJobsPerNetwork() ? job.network : null;
+                ArraySet<JobStatus> batch = mBatches.get(network);
+                if (batch == null) {
+                    batch = new ArraySet<>();
+                    mBatches.put(network, batch);
+                }
+                batch.add(job);
+
                 if (shouldForceBatchJob) {
-                    // Force batching non-ACTIVE jobs. Don't include them in the other counts.
-                    forceBatchedCount++;
                     if (job.getFirstForceBatchedTimeElapsed() == 0) {
                         job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis());
                     }
                 } else {
-                    unbatchedCount++;
+                    mUnbatchedJobCount.put(network,
+                            mUnbatchedJobCount.getOrDefault(job.network, 0) + 1);
                 }
                 if (!isRunning) {
                     runnableJobs.add(job);
+                    if (!shouldForceBatchJob) {
+                        mUnbatchedJobs.add(job);
+                    }
                 }
             } else {
                 if (isRunning) {
@@ -3600,34 +3787,131 @@
         @GuardedBy("mLock")
         @VisibleForTesting
         void postProcessLocked() {
-            if (unbatchedCount > 0
-                    || forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT) {
-                if (DEBUG) {
-                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
+            final ArraySet<JobStatus> jobsToRun = mUnbatchedJobs;
+
+            if (DEBUG) {
+                Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+                        + mUnbatchedJobs.size() + " unbatched jobs.");
+            }
+
+            int unbatchedCount = 0;
+
+            for (int n = mBatches.size() - 1; n >= 0; --n) {
+                final Network network = mBatches.keyAt(n);
+
+                // Count all of the unbatched jobs, including the ones without a network.
+                final Integer unbatchedJobCountObj = mUnbatchedJobCount.get(network);
+                final int unbatchedJobCount;
+                if (unbatchedJobCountObj != null) {
+                    unbatchedJobCount = unbatchedJobCountObj;
+                    unbatchedCount += unbatchedJobCount;
+                } else {
+                    unbatchedJobCount = 0;
                 }
-                noteJobsPending(runnableJobs);
-                mPendingJobQueue.addAll(runnableJobs);
+
+                // Skip the non-networked jobs here. They'll be handled after evaluating
+                // everything else.
+                if (network == null) {
+                    continue;
+                }
+
+                final ArraySet<JobStatus> batchedJobs = mBatches.valueAt(n);
+                if (unbatchedJobCount > 0) {
+                    // Some job is going to activate the network anyway. Might as well run all
+                    // the other jobs that will use this network.
+                    if (DEBUG) {
+                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+                                + batchedJobs.size() + " jobs on " + network
+                                + " because of unbatched job");
+                    }
+                    jobsToRun.addAll(batchedJobs);
+                    continue;
+                }
+
+                final NetworkCapabilities networkCapabilities =
+                        mConnectivityController.getNetworkCapabilities(network);
+                if (networkCapabilities == null) {
+                    Slog.e(TAG, "Couldn't get NetworkCapabilities for network " + network);
+                    continue;
+                }
+
+                final int[] transports = networkCapabilities.getTransportTypes();
+                int maxNetworkBatchReq = 1;
+                for (int transport : transports) {
+                    maxNetworkBatchReq = Math.max(maxNetworkBatchReq,
+                            mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.get(transport));
+                }
+
+                if (batchedJobs.size() >= maxNetworkBatchReq) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+                                + batchedJobs.size()
+                                + " batched network jobs meet requirement for " + network);
+                    }
+                    jobsToRun.addAll(batchedJobs);
+                }
+            }
+
+            final ArraySet<JobStatus> batchedNonNetworkedJobs = mBatches.get(null);
+            if (batchedNonNetworkedJobs != null) {
+                final int minReadyCount = Flags.batchActiveBucketJobs()
+                        ? mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT
+                        : mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT;
+                if (jobsToRun.size() > 0) {
+                    // Some job is going to use the CPU anyway. Might as well run all the other
+                    // CPU-only jobs.
+                    if (DEBUG) {
+                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+                                + batchedNonNetworkedJobs.size() + " non-network jobs");
+                    }
+                    jobsToRun.addAll(batchedNonNetworkedJobs);
+                } else if (batchedNonNetworkedJobs.size() >= minReadyCount) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: adding "
+                                + batchedNonNetworkedJobs.size() + " batched non-network jobs.");
+                    }
+                    jobsToRun.addAll(batchedNonNetworkedJobs);
+                }
+            }
+
+            // In order to properly determine an accurate batch count, the running jobs must be
+            // included in the earlier lists and can only be removed after checking if the batch
+            // count requirement is satisfied.
+            jobsToRun.removeIf(JobSchedulerService.this::isCurrentlyRunningLocked);
+
+            if (unbatchedCount > 0 || jobsToRun.size() > 0) {
+                if (DEBUG) {
+                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running "
+                            + jobsToRun + " jobs.");
+                }
+                noteJobsPending(jobsToRun);
+                mPendingJobQueue.addAll(jobsToRun);
             } else {
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
                 }
-                final int numRunnableJobs = runnableJobs.size();
-                if (numRunnableJobs > 0) {
-                    synchronized (mPendingJobReasonCache) {
-                        for (int i = 0; i < numRunnableJobs; ++i) {
-                            final JobStatus job = runnableJobs.get(i);
-                            SparseIntArray reasons =
-                                    mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
-                            if (reasons == null) {
-                                reasons = new SparseIntArray();
-                                mPendingJobReasonCache
-                                        .add(job.getUid(), job.getNamespace(), reasons);
-                            }
-                            // We're force batching these jobs, so consider it an optimization
-                            // policy reason.
-                            reasons.put(job.getJobId(),
-                                    JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+            }
+
+            // Update the pending reason for any jobs that aren't going to be run.
+            final int numRunnableJobs = runnableJobs.size();
+            if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
+                synchronized (mPendingJobReasonCache) {
+                    for (int i = 0; i < numRunnableJobs; ++i) {
+                        final JobStatus job = runnableJobs.get(i);
+                        if (jobsToRun.contains(job)) {
+                            // We're running this job. Skip updating the pending reason.
+                            continue;
                         }
+                        SparseIntArray reasons =
+                                mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+                        if (reasons == null) {
+                            reasons = new SparseIntArray();
+                            mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
+                        }
+                        // We're force batching these jobs, so consider it an optimization
+                        // policy reason.
+                        reasons.put(job.getJobId(),
+                                JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
                     }
                 }
             }
@@ -3638,9 +3922,10 @@
 
         @VisibleForTesting
         void reset() {
-            forceBatchedCount = 0;
-            unbatchedCount = 0;
             runnableJobs.clear();
+            mBatches.clear();
+            mUnbatchedJobs.clear();
+            mUnbatchedJobCount.clear();
         }
     }
 
@@ -5468,8 +5753,14 @@
 
             pw.println("Aconfig flags:");
             pw.increaseIndent();
+            pw.print(Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS, Flags.batchActiveBucketJobs());
+            pw.println();
+            pw.print(Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK,
+                    Flags.batchConnectivityJobsPerNetwork());
+            pw.println();
             pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
                     Flags.doNotForceRushExecutionAtBoot());
+            pw.println();
             pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
                     Flags.throwOnUnsupportedBiasUsage());
             pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 0cf6a7a..90b4630 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -350,6 +350,12 @@
             case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
                 pw.println(android.app.job.Flags.jobDebugInfoApis());
                 break;
+            case com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS:
+                pw.println(com.android.server.job.Flags.batchActiveBucketJobs());
+                break;
+            case com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK:
+                pw.println(com.android.server.job.Flags.batchConnectivityJobsPerNetwork());
+                break;
             case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
                 pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
                 break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 4f4096f..813cf87 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.ArraySet;
 import android.util.Pools;
 import android.util.SparseArray;
 
@@ -96,10 +97,10 @@
         }
     }
 
-    void addAll(@NonNull List<JobStatus> jobs) {
+    void addAll(@NonNull ArraySet<JobStatus> jobs) {
         final SparseArray<List<JobStatus>> jobsByUid = new SparseArray<>();
         for (int i = jobs.size() - 1; i >= 0; --i) {
-            final JobStatus job = jobs.get(i);
+            final JobStatus job = jobs.valueAt(i);
             List<JobStatus> appJobs = jobsByUid.get(job.getSourceUid());
             if (appJobs == null) {
                 appJobs = new ArrayList<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index f405083..6ed42ec 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -22,11 +22,11 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
-import static com.android.server.job.Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -66,6 +66,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
+import com.android.server.job.Flags;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
 import com.android.server.job.StateControllerProto;
@@ -166,6 +167,10 @@
     @GuardedBy("mLock")
     private final ArrayMap<Network, CachedNetworkMetadata> mAvailableNetworks = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @Nullable
+    private Network mSystemDefaultNetwork;
+
     private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
             new SparseArray<>();
     private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
@@ -286,6 +291,7 @@
     private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
     private static final int MSG_DATA_SAVER_TOGGLED = 2;
     private static final int MSG_UID_POLICIES_CHANGED = 3;
+    private static final int MSG_PROCESS_ACTIVE_NETWORK = 4;
 
     private final Handler mHandler;
 
@@ -313,6 +319,14 @@
         }
     }
 
+    @Override
+    public void startTrackingLocked() {
+        if (Flags.batchConnectivityJobsPerNetwork()) {
+            mConnManager.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+            mConnManager.addDefaultNetworkActiveListener(this);
+        }
+    }
+
     @GuardedBy("mLock")
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
@@ -911,8 +925,8 @@
             return true;
         }
         if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
-            // Exclude VPNs because it's currently not possible to determine the VPN's underlying
-            // network, and thus the correct signal strength of the VPN's network.
+            // VPNs may have multiple underlying networks and determining the correct strength
+            // may not be straightforward.
             // Transmitting data over a VPN is generally more battery-expensive than on the
             // underlying network, so:
             // TODO: find a good way to reduce job use of VPN when it'll be very expensive
@@ -1007,7 +1021,7 @@
             // Need to at least know the estimated download bytes for a prefetch job.
             return false;
         }
-        if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
+        if (Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
             // Since the constraint relaxation isn't required by the job, only do it when the
             // device is charging and the battery level is above the "low battery" threshold.
             if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) {
@@ -1309,7 +1323,7 @@
     }
 
     @Nullable
-    private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
+    public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
         final CachedNetworkMetadata metadata = getNetworkMetadata(network);
         return metadata == null ? null : metadata.networkCapabilities;
     }
@@ -1527,26 +1541,138 @@
     }
 
     /**
+     * Returns {@code true} if the job's assigned network is active or otherwise considered to be
+     * in a good state to run the job now.
+     */
+    @GuardedBy("mLock")
+    public boolean isNetworkInStateForJobRunLocked(@NonNull JobStatus jobStatus) {
+        if (jobStatus.network == null) {
+            return false;
+        }
+        if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.shouldTreatAsUserInitiatedJob()
+                || mService.getUidProcState(jobStatus.getSourceUid())
+                        <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+            // EJs, UIJs, and BFGS+ jobs should be able to activate the network.
+            return true;
+        }
+        return isNetworkInStateForJobRunLocked(jobStatus.network);
+    }
+
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    boolean isNetworkInStateForJobRunLocked(@NonNull Network network) {
+        if (!Flags.batchConnectivityJobsPerNetwork()) {
+            // Active network batching isn't enabled. We don't care about the network state.
+            return true;
+        }
+
+        CachedNetworkMetadata cachedNetworkMetadata = mAvailableNetworks.get(network);
+        if (cachedNetworkMetadata == null) {
+            return false;
+        }
+
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        if (cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+                + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS > nowElapsed) {
+            // Network is still presumed to be active.
+            return true;
+        }
+
+        final boolean inactiveForTooLong =
+                cachedNetworkMetadata.capabilitiesFirstAcquiredTimeElapsed
+                        < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+                && cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+                        < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+        // We can only know the state of the system default network. If that's not available
+        // or the network in question isn't the system default network,
+        // then return true if we haven't gotten an active signal in a long time.
+        if (mSystemDefaultNetwork == null) {
+            return inactiveForTooLong;
+        }
+
+        if (!mSystemDefaultNetwork.equals(network)) {
+            final NetworkCapabilities capabilities = cachedNetworkMetadata.networkCapabilities;
+            if (capabilities != null
+                    && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
+                // VPNs won't have an active signal sent for them. Check their underlying networks
+                // instead, prioritizing the system default if it's one of them.
+                final List<Network> underlyingNetworks = capabilities.getUnderlyingNetworks();
+                if (underlyingNetworks == null) {
+                    return inactiveForTooLong;
+                }
+
+                if (underlyingNetworks.contains(mSystemDefaultNetwork)) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Substituting system default network "
+                                + mSystemDefaultNetwork + " for VPN " + network);
+                    }
+                    return isNetworkInStateForJobRunLocked(mSystemDefaultNetwork);
+                }
+
+                for (int i = underlyingNetworks.size() - 1; i >= 0; --i) {
+                    if (isNetworkInStateForJobRunLocked(underlyingNetworks.get(i))) {
+                        return true;
+                    }
+                }
+            }
+            return inactiveForTooLong;
+        }
+
+        if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+                + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS < nowElapsed) {
+            // We haven't checked the state recently enough. Let's check if the network is active.
+            // However, if we checked after the last confirmed active time and it wasn't active,
+            // then the network is still not active (we would be told when it becomes active
+            // via onNetworkActive()).
+            if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+                    > cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed) {
+                return inactiveForTooLong;
+            }
+            // We need to explicitly check because there's no callback telling us when the network
+            // leaves the high power state.
+            cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed = nowElapsed;
+            final boolean isActive = mConnManager.isDefaultNetworkActive();
+            if (isActive) {
+                cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed = nowElapsed;
+                return true;
+            }
+            return inactiveForTooLong;
+        }
+
+        // We checked the state recently enough, but the network wasn't active. Assume it still
+        // isn't active.
+        return false;
+    }
+
+    /**
      * We know the network has just come up. We want to run any jobs that are ready.
      */
     @Override
     public void onNetworkActive() {
         synchronized (mLock) {
-            for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
-                final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
-                for (int j = jobs.size() - 1; j >= 0; j--) {
-                    final JobStatus js = jobs.valueAt(j);
-                    if (js.isReady()) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Running " + js + " due to network activity.");
-                        }
-                        mStateChangedListener.onRunJobNow(js);
-                    }
-                }
+            if (mSystemDefaultNetwork == null) {
+                Slog.wtf(TAG, "System default network is unknown but active");
+                return;
             }
+
+            CachedNetworkMetadata cachedNetworkMetadata =
+                    mAvailableNetworks.get(mSystemDefaultNetwork);
+            if (cachedNetworkMetadata == null) {
+                Slog.wtf(TAG, "System default network capabilities are unknown but active");
+                return;
+            }
+
+            // This method gets called on the system's main thread (not the
+            // AppSchedulingModuleThread), so shift the processing work to a handler to avoid
+            // blocking important operations on the main thread.
+            cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed =
+                    cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed =
+                            sElapsedRealtimeClock.millis();
+            mHandler.sendEmptyMessage(MSG_PROCESS_ACTIVE_NETWORK);
         }
     }
 
+    /** NetworkCallback to track all network changes. */
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
         @Override
         public void onAvailable(Network network) {
@@ -1565,6 +1691,7 @@
                 CachedNetworkMetadata cnm = mAvailableNetworks.get(network);
                 if (cnm == null) {
                     cnm = new CachedNetworkMetadata();
+                    cnm.capabilitiesFirstAcquiredTimeElapsed = sElapsedRealtimeClock.millis();
                     mAvailableNetworks.put(network, cnm);
                 } else {
                     final NetworkCapabilities oldCaps = cnm.networkCapabilities;
@@ -1700,6 +1827,29 @@
         }
     };
 
+    /** NetworkCallback to track only changes to the default network. */
+    private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onAvailable(Network network) {
+            if (DEBUG) Slog.v(TAG, "systemDefault-onAvailable: " + network);
+            synchronized (mLock) {
+                mSystemDefaultNetwork = network;
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            if (DEBUG) {
+                Slog.v(TAG, "systemDefault-onLost: " + network);
+            }
+            synchronized (mLock) {
+                if (network.equals(mSystemDefaultNetwork)) {
+                    mSystemDefaultNetwork = null;
+                }
+            }
+        }
+    };
+
     private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
         @Override
         public void onRestrictBackgroundChanged(boolean restrictBackground) {
@@ -1762,6 +1912,66 @@
                             }
                         }
                         break;
+
+                    case MSG_PROCESS_ACTIVE_NETWORK:
+                        removeMessages(MSG_PROCESS_ACTIVE_NETWORK);
+                        synchronized (mLock) {
+                            if (mSystemDefaultNetwork == null) {
+                                break;
+                            }
+                            if (!Flags.batchConnectivityJobsPerNetwork()) {
+                                break;
+                            }
+                            if (!isNetworkInStateForJobRunLocked(mSystemDefaultNetwork)) {
+                                break;
+                            }
+
+                            final ArrayMap<Network, Boolean> includeInProcessing = new ArrayMap<>();
+                            // Try to get the jobs to piggyback on the active network.
+                            for (int u = mTrackedJobs.size() - 1; u >= 0; --u) {
+                                final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(u);
+                                for (int j = jobs.size() - 1; j >= 0; --j) {
+                                    final JobStatus js = jobs.valueAt(j);
+                                    if (!mSystemDefaultNetwork.equals(js.network)) {
+                                        final NetworkCapabilities capabilities =
+                                                getNetworkCapabilities(js.network);
+                                        if (capabilities == null
+                                                || !capabilities.hasTransport(
+                                                NetworkCapabilities.TRANSPORT_VPN)) {
+                                            includeInProcessing.put(js.network, Boolean.FALSE);
+                                            continue;
+                                        }
+                                        if (includeInProcessing.containsKey(js.network)) {
+                                            if (!includeInProcessing.get(js.network)) {
+                                                continue;
+                                            }
+                                        } else {
+                                            // VPNs most likely use the system default network as
+                                            // their underlying network. If so, process the job.
+                                            final List<Network> underlyingNetworks =
+                                                    capabilities.getUnderlyingNetworks();
+                                            final boolean isSystemDefaultInUnderlying =
+                                                    underlyingNetworks != null
+                                                            && underlyingNetworks.contains(
+                                                                    mSystemDefaultNetwork);
+                                            includeInProcessing.put(js.network,
+                                                    isSystemDefaultInUnderlying);
+                                            if (!isSystemDefaultInUnderlying) {
+                                                continue;
+                                            }
+                                        }
+                                    }
+                                    if (js.isReady()) {
+                                        if (DEBUG) {
+                                            Slog.d(TAG, "Potentially running " + js
+                                                    + " due to network activity");
+                                        }
+                                        mStateChangedListener.onRunJobNow(js);
+                                    }
+                                }
+                            }
+                        }
+                        break;
                 }
             }
         }
@@ -1782,8 +1992,15 @@
         @VisibleForTesting
         static final String KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY =
                 CC_CONFIG_PREFIX + "avoid_undefined_transport_affinity";
+        private static final String KEY_NETWORK_ACTIVATION_EXPIRATION_MS =
+                CC_CONFIG_PREFIX + "network_activation_expiration_ms";
+        private static final String KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+                CC_CONFIG_PREFIX + "network_activation_max_wait_time_ms";
 
         private static final boolean DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY = false;
+        private static final long DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS = 10000L;
+        private static final long DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+                31 * MINUTE_IN_MILLIS;
 
         /**
          * If true, will avoid network transports that don't have an explicitly defined affinity.
@@ -1791,6 +2008,19 @@
         public boolean AVOID_UNDEFINED_TRANSPORT_AFFINITY =
                 DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY;
 
+        /**
+         * Amount of time that needs to pass before needing to determine if the network is still
+         * active.
+         */
+        public long NETWORK_ACTIVATION_EXPIRATION_MS = DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS;
+
+        /**
+         * Max time to wait since the network was last activated before deciding to allow jobs to
+         * run even if the network isn't active
+         */
+        public long NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+                DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+
         @GuardedBy("mLock")
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
@@ -1803,6 +2033,22 @@
                         mShouldReprocessNetworkCapabilities = true;
                     }
                     break;
+                case KEY_NETWORK_ACTIVATION_EXPIRATION_MS:
+                    final long gracePeriodMs = properties.getLong(key,
+                            DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS);
+                    if (NETWORK_ACTIVATION_EXPIRATION_MS != gracePeriodMs) {
+                        NETWORK_ACTIVATION_EXPIRATION_MS = gracePeriodMs;
+                        // This doesn't need to trigger network capability reprocessing.
+                    }
+                    break;
+                case KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS:
+                    final long maxWaitMs = properties.getLong(key,
+                            DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS);
+                    if (NETWORK_ACTIVATION_MAX_WAIT_TIME_MS != maxWaitMs) {
+                        NETWORK_ACTIVATION_MAX_WAIT_TIME_MS = maxWaitMs;
+                        mShouldReprocessNetworkCapabilities = true;
+                    }
+                    break;
             }
         }
 
@@ -1814,6 +2060,10 @@
 
             pw.print(KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY,
                     AVOID_UNDEFINED_TRANSPORT_AFFINITY).println();
+            pw.print(KEY_NETWORK_ACTIVATION_EXPIRATION_MS,
+                    NETWORK_ACTIVATION_EXPIRATION_MS).println();
+            pw.print(KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS,
+                    NETWORK_ACTIVATION_MAX_WAIT_TIME_MS).println();
 
             pw.decreaseIndent();
         }
@@ -1925,11 +2175,24 @@
     private static class CachedNetworkMetadata {
         public NetworkCapabilities networkCapabilities;
         public boolean satisfiesTransportAffinities;
+        /**
+         * Track the first time ConnectivityController was informed about the capabilities of the
+         * network after it became available.
+         */
+        public long capabilitiesFirstAcquiredTimeElapsed;
+        public long defaultNetworkActivationLastCheckTimeElapsed;
+        public long defaultNetworkActivationLastConfirmedTimeElapsed;
 
         public String toString() {
             return "CNM{"
                     + networkCapabilities.toString()
                     + ", satisfiesTransportAffinities=" + satisfiesTransportAffinities
+                    + ", capabilitiesFirstAcquiredTimeElapsed="
+                            + capabilitiesFirstAcquiredTimeElapsed
+                    + ", defaultNetworkActivationLastCheckTimeElapsed="
+                            + defaultNetworkActivationLastCheckTimeElapsed
+                    + ", defaultNetworkActivationLastConfirmedTimeElapsed="
+                            + defaultNetworkActivationLastConfirmedTimeElapsed
                     + "}";
         }
     }
@@ -2017,7 +2280,7 @@
         pw.println("Aconfig flags:");
         pw.increaseIndent();
         pw.print(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER,
-                relaxPrefetchConnectivityConstraintOnlyOnCharger());
+                Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger());
         pw.println();
         pw.decreaseIndent();
         pw.println();
diff --git a/api/Android.bp b/api/Android.bp
index b3b18b6..a148cbd 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -115,6 +115,7 @@
         "framework-pdf",
         "framework-permission",
         "framework-permission-s",
+        "framework-profiling",
         "framework-scheduling",
         "framework-sdkextensions",
         "framework-statsd",
@@ -199,7 +200,7 @@
     out: ["current.srcjar"],
     cmd: "$(location merge_zips) $(out) $(in)",
     srcs: [
-        ":api-stubs-docs-non-updatable",
+        ":api-stubs-docs-non-updatable{.exportable}",
         ":all-modules-public-stubs-source",
     ],
     visibility: ["//visibility:private"], // Used by make module in //development, mind
@@ -222,7 +223,7 @@
     name: "sdk-annotations.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs{.annotations.zip}",
+        ":android-non-updatable-doc-stubs{.exportable.annotations.zip}",
         ":all-modules-public-annotations",
     ],
 }
@@ -231,7 +232,7 @@
     name: "sdk-annotations-system.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs-system{.annotations.zip}",
+        ":android-non-updatable-doc-stubs-system{.exportable.annotations.zip}",
         ":all-modules-system-annotations",
     ],
 }
@@ -240,7 +241,7 @@
     name: "sdk-annotations-module-lib.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs-module-lib{.annotations.zip}",
+        ":android-non-updatable-doc-stubs-module-lib{.exportable.annotations.zip}",
         ":all-modules-module-lib-annotations",
     ],
 }
@@ -249,7 +250,7 @@
     name: "sdk-annotations-system-server.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs-system-server{.annotations.zip}",
+        ":android-non-updatable-doc-stubs-system-server{.exportable.annotations.zip}",
         ":all-modules-system-server-annotations",
     ],
 }
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 7ae3224..e8fcf4b 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -67,6 +67,7 @@
         ":framework-ondevicepersonalization-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
+        ":framework-profiling-sources",
         ":framework-scheduling-sources",
         ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
@@ -220,7 +221,7 @@
     name: "offline-sdk-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc offline",
@@ -241,7 +242,7 @@
     name: "offline-sdk-referenceonly-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc offline",
@@ -285,7 +286,7 @@
     name: "ds-docs-java",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
@@ -319,7 +320,7 @@
 droiddoc {
     name: "ds-docs-kt",
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     flags: [
         "-noJdkLink",
@@ -373,7 +374,7 @@
     name: "ds-static-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
@@ -390,7 +391,7 @@
     name: "ds-ref-navtree-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 59c0128..852abdf 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -961,7 +961,7 @@
 java_library {
     name: "android_stubs_current_with_test_libs",
     static_libs: [
-        "android_stubs_current",
+        "android_stubs_current_exportable",
         "android.test.base.stubs",
         "android.test.mock.stubs",
         "android.test.runner.stubs",
@@ -976,7 +976,7 @@
 java_library {
     name: "android_system_stubs_current_with_test_libs",
     static_libs: [
-        "android_system_stubs_current",
+        "android_system_stubs_current_exportable",
         "android.test.base.stubs.system",
         "android.test.mock.stubs.system",
         "android.test.runner.stubs.system",
@@ -991,7 +991,7 @@
 java_library {
     name: "android_module_stubs_current_with_test_libs",
     static_libs: [
-        "android_module_lib_stubs_current",
+        "android_module_lib_stubs_current_exportable",
         "android.test.base.stubs",
         "android.test.mock.stubs",
         "android.test.runner.stubs",
@@ -1006,7 +1006,7 @@
 java_library {
     name: "android_system_server_stubs_current_with_test_libs",
     static_libs: [
-        "android_system_server_stubs_current",
+        "android_system_server_stubs_current_exportable",
         "android.test.base.stubs.system",
         "android.test.mock.stubs.system",
         "android.test.runner.stubs.system",
diff --git a/boot/Android.bp b/boot/Android.bp
index 228d060..cdfa7c80 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -122,6 +122,10 @@
             module: "com.android.permission-bootclasspath-fragment",
         },
         {
+            apex: "com.android.profiling",
+            module: "com.android.profiling-bootclasspath-fragment",
+        },
+        {
             apex: "com.android.scheduling",
             module: "com.android.scheduling-bootclasspath-fragment",
         },
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 787055c..25d3a34 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -172,8 +172,10 @@
                         RuntimeException ex = new RuntimeException(
                                 "Could not create uinput device \"" + name + "\"");
                         Log.e(TAG, "Couldn't create uinput device, exiting.", ex);
+                        args.recycle();
                         throw ex;
                     }
+                    args.recycle();
                     break;
                 case MSG_INJECT_EVENT:
                     if (mPtr != 0) {
diff --git a/core/api/current.txt b/core/api/current.txt
index cf5a261..c896d5c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@
     field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
     field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
     field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+    field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
     field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
     field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
     field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -89,6 +90,7 @@
     field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
     field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
     field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
+    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
     field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
     field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
     field public static final String DUMP = "android.permission.DUMP";
@@ -504,6 +506,7 @@
     field public static final int autoSizeTextType = 16844085; // 0x1010535
     field public static final int autoStart = 16843445; // 0x10102b5
     field @Deprecated public static final int autoText = 16843114; // 0x101016a
+    field @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") public static final int autoTransact;
     field public static final int autoUrlDetect = 16843404; // 0x101028c
     field public static final int autoVerify = 16844014; // 0x10104ee
     field public static final int autofillHints = 16844118; // 0x1010556
@@ -1794,6 +1797,7 @@
     field public static final int updatePeriodMillis = 16843344; // 0x1010250
     field public static final int use32bitAbi = 16844053; // 0x1010515
     field public static final int useAppZygote = 16844183; // 0x1010597
+    field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int useBoundsForWidth;
     field public static final int useDefaultMargins = 16843641; // 0x1010379
     field public static final int useEmbeddedDex = 16844190; // 0x101059e
     field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
@@ -4376,6 +4380,7 @@
     method public android.transition.TransitionManager getContentTransitionManager();
     method @Nullable public android.view.View getCurrentFocus();
     method @Deprecated public android.app.FragmentManager getFragmentManager();
+    method @FlaggedApi("android.security.content_uri_permission_apis") @NonNull public android.app.ComponentCaller getInitialCaller();
     method public android.content.Intent getIntent();
     method @Nullable public Object getLastNonConfigurationInstance();
     method @Nullable public String getLaunchedFromPackage();
@@ -5416,6 +5421,12 @@
     field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
   }
 
+  @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller {
+    ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder);
+    method @Nullable public String getPackage();
+    method public int getUid();
+  }
+
   public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
     ctor public DatePickerDialog(@NonNull android.content.Context);
     ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int);
@@ -6878,6 +6889,7 @@
     method public CharSequence getName();
     method @Nullable public String getParentChannelId();
     method public android.net.Uri getSound();
+    method @FlaggedApi("android.app.notification_channel_vibration_effect_api") @Nullable public android.os.VibrationEffect getVibrationEffect();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
@@ -6897,6 +6909,7 @@
     method public void setName(CharSequence);
     method public void setShowBadge(boolean);
     method public void setSound(android.net.Uri, android.media.AudioAttributes);
+    method @FlaggedApi("android.app.notification_channel_vibration_effect_api") public void setVibrationEffect(@Nullable android.os.VibrationEffect);
     method public void setVibrationPattern(long[]);
     method public boolean shouldShowLights();
     method public boolean shouldVibrate();
@@ -9344,9 +9357,12 @@
     method public long getDataBytes();
     method public long getExternalCacheBytes();
     method public void writeToParcel(android.os.Parcel, int);
-    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
-    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
-    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3; // 0x3
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2; // 0x2
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0; // 0x0
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4; // 0x4
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1; // 0x1
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 5; // 0x5
     field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
   }
 
@@ -9510,8 +9526,8 @@
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
     method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
     method public boolean isRequestPinAppWidgetSupported();
-    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
-    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
+    method public void notifyAppWidgetViewDataChanged(int[], int);
+    method public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -12394,7 +12410,7 @@
     method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
-    method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
+    method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
     method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
     method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -12408,6 +12424,12 @@
     field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
+  @FlaggedApi("android.content.pm.archiving") public static class LauncherApps.ArchiveCompatibilityParams {
+    ctor public LauncherApps.ArchiveCompatibilityParams();
+    method public void setEnableIconOverlay(boolean);
+    method public void setEnableUnarchivalConfirmation(boolean);
+  }
+
   public abstract static class LauncherApps.Callback {
     ctor public LauncherApps.Callback();
     method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -13837,6 +13859,7 @@
     method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
     method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method @FlaggedApi("android.content.res.register_resource_paths") public static void registerResourcePaths(@NonNull String, @NonNull android.content.pm.ApplicationInfo);
     method public void removeLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
     method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
     field @AnyRes public static final int ID_NULL = 0; // 0x0
@@ -16502,8 +16525,8 @@
     field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
     field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
     field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
     field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
   }
 
@@ -17980,7 +18003,7 @@
     field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -19149,11 +19172,13 @@
   }
 
   public final class CameraExtensionCharacteristics {
+    method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") public <T> T get(int, @NonNull android.hardware.camera2.CameraCharacteristics.Key<T>);
     method @NonNull public java.util.Set<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(int);
     method @NonNull public java.util.Set<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(int);
     method @Nullable public android.util.Range<java.lang.Long> getEstimatedCaptureLatencyRangeMillis(int, @NonNull android.util.Size, int);
     method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
     method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
+    method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public java.util.Set<android.hardware.camera2.CameraCharacteristics.Key> getKeys(int);
     method @NonNull public java.util.List<android.util.Size> getPostviewSupportedSizes(int, @NonNull android.util.Size, int);
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
     method public boolean isCaptureProcessProgressAvailable(int);
@@ -20264,6 +20289,7 @@
     method @Nullable public android.hardware.input.HostUsiVersion getHostUsiVersion(@NonNull android.view.Display);
     method @Nullable public android.view.InputDevice getInputDevice(int);
     method public int[] getInputDeviceIds();
+    method @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") @Nullable public android.view.InputDevice.ViewBehavior getInputDeviceViewBehavior(int);
     method @FloatRange(from=0, to=1) public float getMaximumObscuringOpacityForTouch();
     method public boolean isStylusPointerIconEnabled();
     method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
@@ -26606,6 +26632,8 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoRequest> CREATOR;
     field public static final int REQUEST_OPTION_AUTO_UPDATE = 1; // 0x1
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONESHOT = 3; // 0x3
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONEWAY = 2; // 0x2
     field public static final int REQUEST_OPTION_REPEAT = 0; // 0x0
   }
 
@@ -27279,6 +27307,24 @@
     field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1
     field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2
     field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON = "video_unavailable_reason";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
     field public static final int SIGNAL_STRENGTH_LOST = 1; // 0x1
     field public static final int SIGNAL_STRENGTH_STRONG = 3; // 0x3
     field public static final int SIGNAL_STRENGTH_WEAK = 2; // 0x2
@@ -27316,6 +27362,7 @@
     field public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = 18; // 0x12
     field public static final int VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 6; // 0x6
     field public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5; // 0x5
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19; // 0x13
     field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1
     field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2; // 0x2
@@ -27393,6 +27440,7 @@
     method public void notifyTuned(@NonNull android.net.Uri);
     method public void notifyTvMessage(int, @NonNull android.os.Bundle);
     method public void notifyVideoAvailable();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
     method public void notifyVideoUnavailable(int);
     method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer);
     method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27407,6 +27455,7 @@
     method public void onRemoveBroadcastInfo(int);
     method public void onRequestAd(@NonNull android.media.tv.AdRequest);
     method public void onRequestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onResumePlayback();
     method public boolean onSelectAudioPresentation(int, int);
     method public boolean onSelectTrack(int, @Nullable String);
     method public abstract void onSetCaptionEnabled(boolean);
@@ -27414,6 +27463,7 @@
     method public abstract void onSetStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
     method public void onSetTvMessageEnabled(int, boolean);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onStopPlayback(int);
     method public void onSurfaceChanged(int, int, int);
     method public long onTimeShiftGetCurrentPosition();
     method public long onTimeShiftGetStartPosition();
@@ -27427,8 +27477,10 @@
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public abstract boolean onTune(android.net.Uri);
     method public boolean onTune(android.net.Uri, android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void onTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
     method public void onTvMessage(int, @NonNull android.os.Bundle);
     method public void onUnblockContent(android.media.tv.TvContentRating);
+    method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void sendTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
     method public void setOverlayViewEnabled(boolean);
   }
 
@@ -27548,6 +27600,7 @@
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
     method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
     method public void reset();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void resumePlayback();
     method public void selectAudioPresentation(int, int);
     method public void selectTrack(int, String);
     method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27558,8 +27611,10 @@
     method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
     method public void setTvMessageEnabled(int, boolean);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setVideoFrozen(boolean);
     method public void setZOrderMediaOverlay(boolean);
     method public void setZOrderOnTop(boolean);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void stopPlayback(int);
     method public void timeShiftPause();
     method public void timeShiftPlay(String, android.net.Uri);
     method public void timeShiftResume();
@@ -27600,6 +27655,7 @@
     method public void onTuned(@NonNull String, @NonNull android.net.Uri);
     method public void onTvMessage(@NonNull String, int, @NonNull android.os.Bundle);
     method public void onVideoAvailable(String);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(@NonNull String, boolean);
     method public void onVideoSizeChanged(String, int, int);
     method public void onVideoUnavailable(String, int);
   }
@@ -27609,6 +27665,104 @@
 package android.media.tv.ad {
 
   @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
+    method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
+    method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
+    method public void unregisterCallback(@NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.ad.action.APP_LINK_COMMAND";
+    field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+    field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+    field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+    field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+    field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+    field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+    field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+    field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+    field public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+    field public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+    field public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+    field public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+    field public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+    field public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+    field public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+    field public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+    field public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = "remove_broadcast_info_request";
+  }
+
+  public abstract static class TvAdManager.TvAdServiceCallback {
+    ctor public TvAdManager.TvAdServiceCallback();
+    method public void onAdServiceAdded(@NonNull String);
+    method public void onAdServiceRemoved(@NonNull String);
+    method public void onAdServiceUpdated(@NonNull String);
+  }
+
+  @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public abstract class TvAdService extends android.app.Service {
+    ctor public TvAdService();
+    method public void onAppLinkCommand(@NonNull android.os.Bundle);
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public abstract android.media.tv.ad.TvAdService.Session onCreateSession(@NonNull String, @NonNull String);
+    field public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+    field public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
+  }
+
+  public abstract static class TvAdService.Session implements android.view.KeyEvent.Callback {
+    ctor public TvAdService.Session(@NonNull android.content.Context);
+    method public boolean isMediaViewEnabled();
+    method @CallSuper public void layoutSurface(int, int, int, int);
+    method @Nullable public android.view.View onCreateMediaView();
+    method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
+    method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
+    method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
+    method public boolean onKeyMultiple(int, int, @Nullable android.view.KeyEvent);
+    method public boolean onKeyUp(int, @Nullable android.view.KeyEvent);
+    method public void onMediaViewSizeChanged(@Px int, @Px int);
+    method public abstract void onRelease();
+    method public void onResetAdService();
+    method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+    method public void onStartAdService();
+    method public void onStopAdService();
+    method public void onSurfaceChanged(int, int, int);
+    method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
+    method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
+    method public void onTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
+    method public void sendTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
+    method @CallSuper public void setMediaViewEnabled(boolean);
+  }
+
+  @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdServiceInfo implements android.os.Parcelable {
+    ctor public TvAdServiceInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName);
+    method public int describeContents();
+    method @NonNull public String getId();
+    method @Nullable public android.content.pm.ServiceInfo getServiceInfo();
+    method @NonNull public java.util.List<java.lang.String> getSupportedTypes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.ad.TvAdServiceInfo> CREATOR;
+  }
+
+  @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdView extends android.view.ViewGroup {
+    ctor public TvAdView(@NonNull android.content.Context);
+    ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
+    ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+    method public void clearOnUnhandledInputEventListener();
+    method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
+    method @Nullable public android.media.tv.ad.TvAdView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
+    method public void onAttachedToWindow();
+    method public void onDetachedFromWindow();
+    method public void onLayout(boolean, int, int, int, int);
+    method public void onMeasure(int, int);
+    method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
+    method public void onVisibilityChanged(@NonNull android.view.View, int);
+    method public void prepareAdService(@NonNull String, @NonNull String);
+    method public void reset();
+    method public void resetAdService();
+    method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+    method public boolean setTvView(@Nullable android.media.tv.TvView);
+    method public void startAdService();
+    method public void stopAdService();
+  }
+
+  public static interface TvAdView.OnUnhandledInputEventListener {
+    method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
   }
 
 }
@@ -27721,6 +27875,7 @@
     method public void onAdResponse(@NonNull android.media.tv.AdResponse);
     method public void onAvailableSpeeds(@NonNull float[]);
     method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
     method public void onContentAllowed();
     method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
     method public void onCreateBiInteractiveAppRequest(@NonNull android.net.Uri, @Nullable android.os.Bundle);
@@ -27746,6 +27901,7 @@
     method public void onRecordingTuned(@NonNull String, @NonNull android.net.Uri);
     method public abstract void onRelease();
     method public void onResetInteractiveApp();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onSelectedTrackInfo(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
     method public void onSetTeletextAppEnabled(boolean);
     method public void onSignalStrength(int);
@@ -27769,18 +27925,22 @@
     method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
     method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
     method public void onVideoAvailable();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(boolean);
     method public void onVideoUnavailable(int);
     method @CallSuper public void removeBroadcastInfo(int);
     method @CallSuper public void requestAd(@NonNull android.media.tv.AdRequest);
     method @CallSuper public void requestAvailableSpeeds();
     method @CallSuper public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestCertificate(@NonNull String, int);
     method @CallSuper public void requestCurrentChannelLcn();
     method @CallSuper public void requestCurrentChannelUri();
     method @CallSuper public void requestCurrentTvInputId();
     method @CallSuper public void requestCurrentVideoBounds();
     method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
     method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
     method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
     method @CallSuper public void requestStopRecording(@NonNull String);
     method @CallSuper public void requestStreamVolume();
@@ -27830,6 +27990,7 @@
     method public void notifyTimeShiftStartPositionChanged(@NonNull String, long);
     method public void notifyTimeShiftStatusChanged(@NonNull String, int);
     method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -27840,10 +28001,12 @@
     method public void reset();
     method public void resetInteractiveApp();
     method public void sendAvailableSpeeds(@NonNull float[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
     method public void sendCurrentChannelLcn(int);
     method public void sendCurrentChannelUri(@Nullable android.net.Uri);
     method public void sendCurrentTvInputId(@Nullable String);
     method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendSelectedTrackInfo(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
     method public void sendSigningResult(@NonNull String, @NonNull byte[]);
     method public void sendStreamVolume(float);
     method public void sendTimeShiftMode(int);
@@ -27873,13 +28036,16 @@
     method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
     method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
     method public void onRequestAvailableSpeeds(@NonNull String);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestCertificate(@NonNull String, @NonNull String, int);
     method public void onRequestCurrentChannelLcn(@NonNull String);
     method public void onRequestCurrentChannelUri(@NonNull String);
     method public void onRequestCurrentTvInputId(@NonNull String);
     method public void onRequestCurrentVideoBounds(@NonNull String);
     method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.os.Bundle);
     method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
     method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
     method public void onRequestStopRecording(@NonNull String, @NonNull String);
     method public void onRequestStreamVolume(@NonNull String);
@@ -42299,6 +42465,8 @@
     method public void onConference(android.telecom.Connection, android.telecom.Connection);
     method public void onConnectionServiceFocusGained();
     method public void onConnectionServiceFocusLost();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public void onCreateConferenceComplete(@NonNull android.telecom.Conference);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public void onCreateConnectionComplete(@NonNull android.telecom.Connection);
     method @Nullable public android.telecom.Conference onCreateIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
     method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
@@ -43466,13 +43634,44 @@
   }
 
   public static final class CarrierConfigManager.ImsEmergency {
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int DOMAIN_CS = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int DOMAIN_PS_3GPP = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int DOMAIN_PS_NON_3GPP = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT = "imsemergency.cross_stack_redial_timer_sec_int";
     field public static final String KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL = "imsemergency.emergency_callback_mode_supported_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT = "imsemergency.emergency_call_setup_timer_on_current_network_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY = "imsemergency.emergency_cdma_preferred_numbers_string_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY = "imsemergency.emergency_domain_preference_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY = "imsemergency.emergency_domain_preference_roaming_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL = "imsemergency.emergency_lte_preferred_after_nr_failed_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT = "imsemergency.emergency_network_scan_type_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_cs_roaming_supported_access_network_types_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_cs_supported_access_network_types_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = "imsemergency.emergency_over_ims_supported_3gpp_network_types_int_array";
     field public static final String KEY_EMERGENCY_OVER_IMS_SUPPORTED_RATS_INT_ARRAY = "imsemergency.emergency_over_ims_supported_rats_int_array";
     field public static final String KEY_EMERGENCY_QOS_PRECONDITION_SUPPORTED_BOOL = "imsemergency.emergency_qos_precondition_supported_bool";
     field public static final String KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT = "imsemergency.emergency_registration_timer_millis_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL = "imsemergency.emergency_requires_ims_registration_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL = "imsemergency.emergency_requires_volte_enabled_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT = "imsemergency.emergency_scan_timer_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT = "imsemergency.emergency_vowifi_requires_condition_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT = "imsemergency.maximum_cellular_search_timer_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT = "imsemergency.maximum_number_of_emergency_tries_over_vowifi_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL = "imsemergency.prefer_ims_emergency_when_voice_calls_on_cs_bool";
     field public static final String KEY_PREFIX = "imsemergency.";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT = "imsemergency.quick_cross_stack_redial_timer_sec_int";
     field public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT = "imsemergency.refresh_geolocation_timeout_millis_int";
     field public static final String KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL = "imsemergency.retry_emergency_on_ims_pdn_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL = "imsemergency.scan_limited_service_after_volte_failure_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL = "imsemergency.start_quick_cross_stack_redial_timer_when_registered_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int REDIAL_TIMER_DISABLED = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int SCAN_TYPE_FULL_SERVICE = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int SCAN_TYPE_NO_PREFERENCE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int VOWIFI_REQUIRES_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int VOWIFI_REQUIRES_VALID_EID = 2; // 0x2
   }
 
   public static final class CarrierConfigManager.ImsRtt {
@@ -44544,6 +44743,7 @@
     method @Nullable public android.telephony.CellIdentity getCellIdentity();
     method public int getDomain();
     method @Nullable public String getRegisteredPlmn();
+    method @FlaggedApi("com.android.internal.telephony.flags.network_registration_info_reject_cause") public int getRejectCause();
     method public int getTransportType();
     method public boolean isNetworkRegistered();
     method public boolean isNetworkRoaming();
@@ -45154,13 +45354,13 @@
     method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public android.telephony.SubscriptionManager createForAllUserProfiles();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
-    method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
+    method @Nullable public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
     method public static int getActiveDataSubscriptionId();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfo(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getActiveSubscriptionInfoCount();
     method public int getActiveSubscriptionInfoCountMax();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public java.util.List<android.telephony.SubscriptionInfo> getAllSubscriptionInfoList();
     method @NonNull public java.util.List<android.telephony.SubscriptionInfo> getCompleteActiveSubscriptionInfoList();
     method public static int getDefaultDataSubscriptionId();
@@ -47103,7 +47303,7 @@
     method public int getLineForOffset(int);
     method public int getLineForVertical(int);
     method public float getLineLeft(int);
-    method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
+    method @FlaggedApi("com.android.text.flags.letter_spacing_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
     method public float getLineMax(int);
     method public float getLineRight(int);
     method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -47154,7 +47354,7 @@
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -50434,6 +50634,10 @@
     method public boolean isFromSource(int);
   }
 
+  @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") public static final class InputDevice.ViewBehavior {
+    method @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") public boolean shouldSmoothScroll(int, int);
+  }
+
   public abstract class InputEvent implements android.os.Parcelable {
     method public int describeContents();
     method public final android.view.InputDevice getDevice();
@@ -53782,6 +53986,7 @@
     method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+    method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
     method @Deprecated public android.view.Display getDefaultDisplay();
     method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53791,11 +53996,13 @@
     method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
     method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
+    method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
     method public void removeViewImmediate(android.view.View);
     method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
     method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+    field @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
     field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
     field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
     field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
@@ -53810,6 +54017,8 @@
     field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
     field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
     field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
+    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
   }
 
   public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
@@ -59523,7 +59732,7 @@
     method public void setRadioGroupChecked(@IdRes int, @IdRes int);
     method public void setRelativeScrollPosition(@IdRes int, int);
     method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
-    method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
+    method public void setRemoteAdapter(@IdRes int, android.content.Intent);
     method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
     method public void setScrollPosition(@IdRes int, int);
     method public void setShort(@IdRes int, String, short);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 55ed1f5..1273da7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -132,6 +132,11 @@
     field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
   }
 
+  @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public class SignedPackage {
+    method @NonNull public byte[] getCertificateDigest();
+    method @NonNull public String getPkgName();
+  }
+
 }
 
 package android.hardware.usb {
@@ -187,6 +192,7 @@
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpInfo(boolean, int);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
+    method @FlaggedApi("android.media.audio.sco_managed_by_audio") @NonNull public static android.media.BluetoothProfileConnectionInfo createHfpInfo();
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioOutputInfo(boolean, int);
     method public int describeContents();
@@ -344,7 +350,9 @@
 package android.os {
 
   public class ArtModuleServiceManager {
+    method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdPreRebootServiceRegisterer();
     method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+    method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getDexoptChrootSetupServiceRegisterer();
   }
 
   public static final class ArtModuleServiceManager.ServiceRegisterer {
@@ -378,6 +386,10 @@
     field public static final int DEVICE_INITIAL_SDK_INT;
   }
 
+  public class Environment {
+    method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory();
+  }
+
   public class IpcDataCache<Query, Result> {
     ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
     method public void disableForCurrentProcess();
@@ -427,6 +439,8 @@
 
   public class SystemConfigManager {
     method @NonNull public java.util.List<android.content.ComponentName> getEnabledComponentOverrides(@NonNull String);
+    method @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public java.util.Set<android.content.pm.SignedPackage> getEnhancedConfirmationTrustedInstallers();
+    method @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public java.util.Set<android.content.pm.SignedPackage> getEnhancedConfirmationTrustedPackages();
   }
 
   public final class Trace {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 86f2b628..515d017 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -287,7 +287,7 @@
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
     field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
     field public static final String READ_GLOBAL_APP_SEARCH_DATA = "android.permission.READ_GLOBAL_APP_SEARCH_DATA";
-    field public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS";
+    field @FlaggedApi("android.content.pm.get_resolved_apk_path") public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS";
     field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
     field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
     field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
@@ -414,6 +414,7 @@
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
     field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
     field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+    field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
   }
 
   public static final class Manifest.permission_group {
@@ -3157,6 +3158,7 @@
     field public static final int STATUS_SUCCESS = 1; // 0x1
     field public static final int STATUS_UNKNOWN = 0; // 0x0
     field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+    field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
     field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
   }
 
@@ -3942,7 +3944,9 @@
     method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
     method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams();
+    method @FlaggedApi("android.content.pm.set_pre_verified_domains") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public java.util.Set<java.lang.String> getPreVerifiedDomains();
     method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String);
+    method @FlaggedApi("android.content.pm.set_pre_verified_domains") @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public void setPreVerifiedDomains(@NonNull java.util.Set<java.lang.String>);
   }
 
   public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -3989,6 +3993,7 @@
     method @NonNull public boolean canUserUninstall(@NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_METADATA) public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") @RequiresPermission(android.Manifest.permission.GET_APP_METADATA) public int getAppMetadataSource(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.dex.ArtManager getArtManager();
@@ -4041,6 +4046,10 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+    field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_APK = 1; // 0x1
+    field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_INSTALLER = 2; // 0x2
+    field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_SYSTEM_IMAGE = 3; // 0x3
+    field @FlaggedApi("android.content.pm.asl_in_apk_app_metadata_source") public static final int APP_METADATA_SOURCE_UNKNOWN = 0; // 0x0
     field public static final int DELETE_ALL_USERS = 2; // 0x2
     field public static final int DELETE_FAILED_ABORTED = -5; // 0xfffffffb
     field public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; // 0xfffffffe
@@ -4390,6 +4399,59 @@
 
 package android.credentials.selection {
 
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class AuthenticationEntry implements android.os.Parcelable {
+    ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int, @NonNull android.content.Intent);
+    method public int describeContents();
+    method @Nullable public android.content.Intent getFrameworkExtrasIntent();
+    method @NonNull public String getKey();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method @NonNull public int getStatus();
+    method @NonNull public String getSubkey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.AuthenticationEntry> CREATOR;
+    field public static final int STATUS_LOCKED = 0; // 0x0
+    field public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1; // 0x1
+    field public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2; // 0x2
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getAppPackageName();
+    method @NonNull public android.credentials.selection.RequestToken getRequestToken();
+    method public boolean shouldShowCancellationExplanation();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.CancelSelectionRequest> CREATOR;
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderInfo {
+    method @NonNull public String getProviderName();
+    method @Nullable public android.credentials.selection.Entry getRemoteEntry();
+    method @NonNull public java.util.List<android.credentials.selection.Entry> getSaveEntries();
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public static final class CreateCredentialProviderInfo.Builder {
+    ctor public CreateCredentialProviderInfo.Builder(@NonNull String);
+    method @NonNull public android.credentials.selection.CreateCredentialProviderInfo build();
+    method @NonNull public android.credentials.selection.CreateCredentialProviderInfo.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
+    method @NonNull public android.credentials.selection.CreateCredentialProviderInfo.Builder setSaveEntries(@NonNull java.util.List<android.credentials.selection.Entry>);
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class DisabledProviderInfo {
+    ctor public DisabledProviderInfo(@NonNull String);
+    method @NonNull public String getProviderName();
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class Entry implements android.os.Parcelable {
+    ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
+    method public int describeContents();
+    method @Nullable public android.content.Intent getFrameworkExtrasIntent();
+    method @NonNull public String getKey();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method @NonNull public String getSubkey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.Entry> CREATOR;
+  }
+
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class FailureResult {
     ctor public FailureResult(int, @Nullable String);
     method public int getErrorCode();
@@ -4399,6 +4461,32 @@
     field public static final int ERROR_CODE_UI_FAILURE = 0; // 0x0
   }
 
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class GetCredentialProviderInfo {
+    method @NonNull public java.util.List<android.credentials.selection.Entry> getActionChips();
+    method @NonNull public java.util.List<android.credentials.selection.AuthenticationEntry> getAuthenticationEntries();
+    method @NonNull public java.util.List<android.credentials.selection.Entry> getCredentialEntries();
+    method @NonNull public String getProviderName();
+    method @Nullable public android.credentials.selection.Entry getRemoteEntry();
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public static final class GetCredentialProviderInfo.Builder {
+    ctor public GetCredentialProviderInfo.Builder(@NonNull String);
+    method @NonNull public android.credentials.selection.GetCredentialProviderInfo build();
+    method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setActionChips(@NonNull java.util.List<android.credentials.selection.Entry>);
+    method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setAuthenticationEntries(@NonNull java.util.List<android.credentials.selection.AuthenticationEntry>);
+    method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setCredentialEntries(@NonNull java.util.List<android.credentials.selection.Entry>);
+    method @NonNull public android.credentials.selection.GetCredentialProviderInfo.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class IntentHelper {
+    method @Nullable public static android.credentials.selection.CancelSelectionRequest extractCancelUiRequest(@NonNull android.content.Intent);
+    method @NonNull public static java.util.List<android.credentials.selection.CreateCredentialProviderInfo> extractCreateCredentialProviderInfoList(@NonNull android.content.Intent);
+    method @NonNull public static java.util.List<android.credentials.selection.DisabledProviderInfo> extractDisabledProviderInfoList(@NonNull android.content.Intent);
+    method @NonNull public static java.util.List<android.credentials.selection.GetCredentialProviderInfo> extractGetCredentialProviderInfoList(@NonNull android.content.Intent);
+    method @Nullable public static android.credentials.selection.RequestInfo extractRequestInfo(@NonNull android.content.Intent);
+    method @Nullable public static android.os.ResultReceiver extractResultReceiver(@NonNull android.content.Intent);
+  }
+
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ProviderPendingIntentResponse implements android.os.Parcelable {
     ctor public ProviderPendingIntentResponse(int, @Nullable android.content.Intent);
     method public int describeContents();
@@ -4408,6 +4496,27 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.ProviderPendingIntentResponse> CREATOR;
   }
 
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getAppPackageName();
+    method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
+    method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
+    method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
+    method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
+    method @NonNull public android.credentials.selection.RequestToken getRequestToken();
+    method @NonNull public String getType();
+    method public boolean hasPermissionToOverrideDefault();
+    method public boolean isShowAllOptionsRequested();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
+    field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
+    field @NonNull public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
+    field @NonNull public static final String TYPE_UNDEFINED = "android.credentials.selection.TYPE_UNDEFINED";
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
+  }
+
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ResultHelper {
     method public static void sendFailureResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.FailureResult);
     method public static void sendUserSelectionResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.UserSelectionResult);
@@ -4579,6 +4688,7 @@
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected AdvancedExtender(@NonNull android.hardware.camera2.CameraManager);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String);
+    method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getMetadataVendorId(@NonNull String);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String);
@@ -10061,6 +10171,7 @@
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilterToAutoTransact(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -10074,6 +10185,7 @@
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean getShouldAutoTransact(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
@@ -10280,6 +10392,7 @@
   public final class ConfigUpdate {
     field public static final String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
     field public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
     field public static final String ACTION_UPDATE_CONVERSATION_ACTIONS = "android.intent.action.UPDATE_CONVERSATION_ACTIONS";
     field public static final String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS";
     field public static final String ACTION_UPDATE_EMERGENCY_NUMBER_DB = "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
@@ -10289,6 +10402,7 @@
     field public static final String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS";
     field public static final String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION";
     field public static final String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES";
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EXTRA_DOMAIN = "android.os.extra.DOMAIN";
     field public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
     field public static final String EXTRA_VERSION = "android.os.extra.VERSION";
   }
@@ -11059,6 +11173,7 @@
     method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
+    method @FlaggedApi("android.os.storage_lifetime_api") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getInternalStorageRemainingLifetime();
     method public static boolean hasIsolatedStorage();
     method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
@@ -11303,6 +11418,12 @@
     field public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
+  @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+  }
+
   @Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
     field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
     field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -13441,6 +13562,7 @@
 
   public abstract class Connection extends android.telecom.Conferenceable {
     method @Deprecated public final android.telecom.AudioState getAudioState();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public final int getCallDirection();
     method @IntRange(from=0) public final long getConnectTimeMillis();
     method public final long getConnectionStartElapsedRealtimeMillis();
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
@@ -13455,7 +13577,9 @@
     method public void setTelecomCallId(@NonNull String);
     field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
     field public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 262144; // 0x40000
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EVENT_CALL_QUALITY_REPORT = "android.telecom.event.CALL_QUALITY_REPORT";
     field public static final String EVENT_DEVICE_TO_DEVICE_MESSAGE = "android.telecom.event.DEVICE_TO_DEVICE_MESSAGE";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_CALL_QUALITY_REPORT = "android.telecom.extra.CALL_QUALITY_REPORT";
     field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_TYPE";
     field public static final String EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE = "android.telecom.extra.DEVICE_TO_DEVICE_MESSAGE_VALUE";
     field public static final String EXTRA_DISABLE_ADD_CALL = "android.telecom.extra.DISABLE_ADD_CALL";
@@ -13723,6 +13847,7 @@
     method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
     field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -14055,6 +14180,73 @@
     method @NonNull public android.telephony.DataThrottlingRequest.Builder setDataThrottlingAction(int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public class DomainSelectionService extends android.app.Service {
+    ctor public DomainSelectionService();
+    method public void onBarringInfoUpdated(int, int, @NonNull android.telephony.BarringInfo);
+    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @NonNull public java.util.concurrent.Executor onCreateExecutor();
+    method public void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback);
+    method public void onServiceStateUpdated(int, int, @NonNull android.telephony.ServiceState);
+    field public static final int SCAN_TYPE_FULL_SERVICE = 2; // 0x2
+    field public static final int SCAN_TYPE_LIMITED_SERVICE = 1; // 0x1
+    field public static final int SCAN_TYPE_NO_PREFERENCE = 0; // 0x0
+    field public static final int SELECTOR_TYPE_CALLING = 1; // 0x1
+    field public static final int SELECTOR_TYPE_SMS = 2; // 0x2
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.Uri getAddress();
+    method @Nullable public String getCallId();
+    method public int getCsDisconnectCause();
+    method @Nullable public android.telephony.EmergencyRegResult getEmergencyRegResult();
+    method @Nullable public android.telephony.ims.ImsReasonInfo getPsDisconnectCause();
+    method public int getSelectorType();
+    method public int getSlotIndex();
+    method public int getSubscriptionId();
+    method public boolean isEmergency();
+    method public boolean isExitedFromAirplaneMode();
+    method public boolean isTestEmergencyNumber();
+    method public boolean isVideoCall();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DomainSelectionService.SelectionAttributes> CREATOR;
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes.Builder {
+    ctor public DomainSelectionService.SelectionAttributes.Builder(int, int, int);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes build();
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@NonNull android.net.Uri);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@NonNull String);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCsDisconnectCause(int);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergency(boolean);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegResult(@NonNull android.telephony.EmergencyRegResult);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setExitedFromAirplaneMode(boolean);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@NonNull android.telephony.ims.ImsReasonInfo);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setTestEmergencyNumber(boolean);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setVideoCall(boolean);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface DomainSelector {
+    method public void finishSelection();
+    method public void reselectDomain(@NonNull android.telephony.DomainSelectionService.SelectionAttributes);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAccessNetwork();
+    method @NonNull public String getCountryIso();
+    method public int getDomain();
+    method @NonNull public String getMcc();
+    method @NonNull public String getMnc();
+    method public int getNwProvidedEmc();
+    method public int getNwProvidedEmf();
+    method public int getRegState();
+    method public boolean isEmcBearerSupported();
+    method public boolean isVopsSupported();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegResult> CREATOR;
+  }
+
   public final class ImsiEncryptionInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getKeyIdentifier();
@@ -14128,7 +14320,6 @@
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getNetworkRegistrationState();
     method @Deprecated public int getRegistrationState();
-    method public int getRejectCause();
     method public int getRoamingType();
     method public boolean isEmergencyEnabled();
     method public void writeToParcel(android.os.Parcel, int);
@@ -14326,6 +14517,8 @@
     field public static final int CHANNEL_UNACCEPTABLE = 6; // 0x6
     field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
     field public static final int DESTINATION_OUT_OF_ORDER = 27; // 0x1b
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int EMERGENCY_PERM_FAILURE = 326; // 0x146
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int EMERGENCY_TEMP_FAILURE = 325; // 0x145
     field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
     field public static final int FACILITY_REJECTED = 29; // 0x1d
     field public static final int FDN_BLOCKED = 241; // 0xf1
@@ -14564,6 +14757,7 @@
     method public boolean areUiccApplicationsEnabled();
     method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
     method public int getProfileClass();
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public int getTransferStatus();
     method public boolean isGroupDisabled();
   }
 
@@ -14573,7 +14767,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getActiveSubscriptionIdList();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public byte[] getAllSimSpecificSettingsForBackup();
-    method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
+    method @Nullable public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
     method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
@@ -14586,6 +14780,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setTransferStatus(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
     field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
     field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
@@ -14595,6 +14790,9 @@
     field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
     field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
     field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_CONVERTED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1; // 0x1
     field @NonNull public static final android.net.Uri VT_ENABLED_CONTENT_URI;
     field @NonNull public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
     field @NonNull public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -14808,6 +15006,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCellularIdentifierDisclosureNotificationsEnabled();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDomainSelectionSupported();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -14827,7 +15026,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
@@ -15006,19 +15205,19 @@
     method public default void onCarrierServiceChanged(@Nullable String, int);
   }
 
-  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams {
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticData {
     method public long getLogcatCollectionStartTimeMillis();
     method public boolean isLogcatCollectionEnabled();
-    method public boolean isTelecomDumpSysCollectionEnabled();
-    method public boolean isTelephonyDumpSysCollectionEnabled();
+    method public boolean isTelecomDumpsysCollectionEnabled();
+    method public boolean isTelephonyDumpsysCollectionEnabled();
   }
 
-  public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder {
-    ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder();
-    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build();
-    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long);
-    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean);
-    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean);
+  public static final class TelephonyManager.EmergencyCallDiagnosticData.Builder {
+    ctor public TelephonyManager.EmergencyCallDiagnosticData.Builder();
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticData build();
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticData.Builder setLogcatCollectionStartTimeMillis(long);
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticData.Builder setTelecomDumpsysCollectionEnabled(boolean);
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticData.Builder setTelephonyDumpsysCollectionEnabled(boolean);
   }
 
   public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
@@ -15032,7 +15231,7 @@
 
   @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public class TelephonyRegistryManager {
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
-    method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
   }
 
   public final class ThermalMitigationRequest implements android.os.Parcelable {
@@ -15053,6 +15252,13 @@
     method @NonNull public android.telephony.ThermalMitigationRequest.Builder setThermalMitigationAction(int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface TransportSelectorCallback {
+    method public void onCreated(@NonNull android.telephony.DomainSelector);
+    method public void onSelectionTerminated(int);
+    method public void onWlanSelected(boolean);
+    method public void onWwanSelected(@NonNull java.util.function.Consumer<android.telephony.WwanSelectorCallback>);
+  }
+
   public final class UiccAccessRule implements android.os.Parcelable {
     ctor public UiccAccessRule(byte[], @Nullable String, long);
     method public int describeContents();
@@ -15108,6 +15314,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.VopsSupportInfo> CREATOR;
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface WwanSelectorCallback {
+    method public void onDomainSelected(int, boolean);
+    method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegResult>);
+  }
+
 }
 
 package android.telephony.cdma {
@@ -15447,7 +15658,9 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getSupportedCountries();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getUnsupportedCountries();
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isPsimConversionSupported(int);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setPsimConversionSupportedCarriers(@NonNull java.util.Set<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
     field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 77add41..4a048bd 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1267,22 +1267,6 @@
 
 package android.credentials.selection {
 
-  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class AuthenticationEntry implements android.os.Parcelable {
-    ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int);
-    ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int, @NonNull android.content.Intent);
-    method public int describeContents();
-    method @Nullable public android.content.Intent getFrameworkExtrasIntent();
-    method @NonNull public String getKey();
-    method @NonNull public android.app.slice.Slice getSlice();
-    method @NonNull public int getStatus();
-    method @NonNull public String getSubkey();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.AuthenticationEntry> CREATOR;
-    field public static final int STATUS_LOCKED = 0; // 0x0
-    field public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1; // 0x1
-    field public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2; // 0x2
-  }
-
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class BaseDialogResult implements android.os.Parcelable {
     ctor public BaseDialogResult(@Nullable android.os.IBinder);
     ctor protected BaseDialogResult(@NonNull android.os.Parcel);
@@ -1298,6 +1282,10 @@
     field public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0; // 0x0
   }
 
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
+    ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public CancelSelectionRequest(@NonNull android.os.IBinder, boolean, @NonNull String);
+  }
+
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
     ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @Nullable android.credentials.selection.Entry);
     method @Nullable public android.credentials.selection.Entry getRemoteEntry();
@@ -1317,19 +1305,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.DisabledProviderData> CREATOR;
   }
 
-  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class Entry implements android.os.Parcelable {
-    ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
-    ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
-    method public int describeContents();
-    method @Nullable public android.content.Intent getFrameworkExtrasIntent();
-    method @NonNull public String getKey();
-    method @Nullable public android.app.PendingIntent getPendingIntent();
-    method @NonNull public android.app.slice.Slice getSlice();
-    method @NonNull public String getSubkey();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.Entry> CREATOR;
-  }
-
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class FailureDialogResult extends android.credentials.selection.BaseDialogResult implements android.os.Parcelable {
     ctor public FailureDialogResult(@Nullable android.os.IBinder, @Nullable String);
     method public static void addToBundle(@NonNull android.credentials.selection.FailureDialogResult, @NonNull android.os.Bundle);
@@ -1357,7 +1332,8 @@
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
-    method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
+    method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String);
+    method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public abstract class ProviderData implements android.os.Parcelable {
@@ -1371,25 +1347,12 @@
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public String getAppPackageName();
-    method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
-    method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
-    method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
-    method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
-    method @NonNull public android.os.IBinder getToken();
-    method @NonNull public String getType();
-    method public boolean hasPermissionToOverrideDefault();
-    method @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String);
-    method @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
-    method @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
-    method @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
-    field @NonNull public static final String EXTRA_REQUEST_INFO = "android.credentials.selection.extra.REQUEST_INFO";
-    field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
-    field @NonNull public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
-    field @NonNull public static final String TYPE_UNDEFINED = "android.credentials.selection.TYPE_UNDEFINED";
+    method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>, boolean);
+    method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean, boolean);
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
+    ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public RequestToken(@NonNull android.os.IBinder);
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class UserSelectionDialogResult extends android.credentials.selection.BaseDialogResult implements android.os.Parcelable {
@@ -1745,6 +1708,7 @@
     method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
     method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
+    method public int getMousePointerSpeed();
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
     method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method public void removeUniqueIdAssociation(@NonNull String);
@@ -1754,6 +1718,7 @@
 
   public class InputSettings {
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
+    field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
   }
 
 }
@@ -2882,6 +2847,10 @@
     field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
   }
 
+  public static final class Settings.System extends android.provider.Settings.NameValueTable {
+    field public static final String POINTER_SPEED = "pointer_speed";
+  }
+
   public static final class Telephony.Sms.Intents {
     field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
   }
@@ -3309,7 +3278,6 @@
     method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getHalVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
     method @Deprecated public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDomainSelectionSupported();
     method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2103055..ab9a4ec 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -844,6 +844,7 @@
     private IBinder mToken;
     private IBinder mAssistToken;
     private IBinder mShareableActivityToken;
+    private ComponentCaller mInitialCaller;
     @UnsupportedAppUsage
     private int mIdent;
     @UnsupportedAppUsage
@@ -7031,6 +7032,20 @@
     }
 
     /**
+     * Returns the ComponentCaller instance of the app that initially launched this activity.
+     *
+     * <p>Note that calls to {@link #onNewIntent} have no effect on the returned value of this
+     * method.
+     *
+     * @return {@link ComponentCaller} instance
+     * @see ComponentCaller
+     */
+    @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    public @NonNull ComponentCaller getInitialCaller() {
+        return mInitialCaller;
+    }
+
+    /**
      * Control whether this activity's main window is visible.  This is intended
      * only for the special case of an activity that is not going to show a
      * UI itself, but can't just finish prior to onResume() because it needs
@@ -8647,6 +8662,19 @@
             Configuration config, String referrer, IVoiceInteractor voiceInteractor,
             Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
             IBinder shareableActivityToken) {
+        attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
+                lastNonConfigurationInstances, config, referrer, voiceInteractor, window,
+                activityConfigCallback, assistToken, shareableActivityToken, null);
+    }
+
+    final void attach(Context context, ActivityThread aThread,
+            Instrumentation instr, IBinder token, int ident,
+            Application application, Intent intent, ActivityInfo info,
+            CharSequence title, Activity parent, String id,
+            NonConfigurationInstances lastNonConfigurationInstances,
+            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
+            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
+            IBinder shareableActivityToken, IBinder initialCallerInfoAccessToken) {
         if (sandboxActivitySdkBasedContext()) {
             // Sandbox activities extract a token from the intent's extra to identify the related
             // SDK as part of overriding attachBaseContext, then it wraps the passed context in an
@@ -8711,6 +8739,10 @@
 
         getAutofillClientController().onActivityAttached(application);
         setContentCaptureOptions(application.getContentCaptureOptions());
+
+        if (android.security.Flags.contentUriPermissionApis()) {
+            mInitialCaller = new ComponentCaller(getActivityToken(), initialCallerInfoAccessToken);
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6285eb3..084c71f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -147,6 +147,7 @@
  * </p>
  */
 @SystemService(Context.ACTIVITY_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class ActivityManager {
     private static String TAG = "ActivityManager";
 
@@ -966,6 +967,7 @@
      * Print capability bits in human-readable form.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -976,6 +978,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -989,6 +992,7 @@
      * Print capability bits in human-readable form.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
         printCapabilitiesSummary(pw, caps);
         final int remain = caps & ~PROCESS_CAPABILITY_ALL;
@@ -999,6 +1003,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String getCapabilitiesSummary(@ProcessCapability int caps) {
         final StringBuilder sb = new StringBuilder();
         printCapabilitiesSummary(sb, caps);
@@ -1018,6 +1023,7 @@
      * @return the value of the corresponding enums.proto ProcessStateEnum value.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int processStateAmToProto(int amInt) {
         switch (amInt) {
             case PROCESS_STATE_UNKNOWN:
@@ -1078,16 +1084,19 @@
     public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
 
     /** @hide Should this process state be considered a background state? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isProcStateBackground(int procState) {
         return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
     }
 
     /** @hide Should this process state be considered in the cache? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isProcStateCached(int procState) {
         return procState >= PROCESS_STATE_CACHED_ACTIVITY;
     }
 
     /** @hide Is this a foreground service type? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isForegroundService(int procState) {
         return procState == PROCESS_STATE_FOREGROUND_SERVICE;
     }
@@ -1161,10 +1170,25 @@
         mContext = context;
     }
 
+    private static volatile int sCurrentUser$ravenwood = UserHandle.USER_NULL;
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood(int currentUser) {
+        sCurrentUser$ravenwood = currentUser;
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sCurrentUser$ravenwood = UserHandle.USER_NULL;
+    }
+
     /**
      * Returns whether the launch was successful.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isStartResultSuccessful(int result) {
         return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
     }
@@ -1173,6 +1197,7 @@
      * Returns whether the launch result was a fatal error.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isStartResultFatalError(int result) {
         return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
     }
@@ -1343,6 +1368,7 @@
     public @interface RestrictionLevel{}
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String restrictionLevelToName(@RestrictionLevel int level) {
         switch (level) {
             case RESTRICTION_LEVEL_UNKNOWN:
@@ -4779,6 +4805,7 @@
      * Returns "true" if the user interface is currently being messed with
      * by a monkey.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isUserAMonkey() {
         try {
             return getService().isUserAMonkey();
@@ -4787,6 +4814,12 @@
         }
     }
 
+    /** @hide */
+    public static boolean isUserAMonkey$ravenwood() {
+        // Ravenwood environment is never considered a "monkey"
+        return false;
+    }
+
     /**
      * Returns "true" if device is running in a test harness.
      *
@@ -4973,6 +5006,7 @@
             "android.permission.INTERACT_ACROSS_USERS",
             "android.permission.INTERACT_ACROSS_USERS_FULL"
     })
+    @android.ravenwood.annotation.RavenwoodReplace
     public static int getCurrentUser() {
         try {
             return getService().getCurrentUserId();
@@ -4981,6 +5015,11 @@
         }
     }
 
+    /** @hide */
+    public static int getCurrentUser$ravenwood() {
+        return sCurrentUser$ravenwood;
+    }
+
     /**
      * @param userid the user's id. Zero indicates the default user.
      * @hide
@@ -5320,6 +5359,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isSystemReady() {
         if (!sSystemReady) {
             if (ActivityThread.isSystem()) {
@@ -5334,6 +5374,12 @@
         return sSystemReady;
     }
 
+    /** @hide */
+    public static boolean isSystemReady$ravenwood() {
+        // Ravenwood environment is always considered as booted and ready
+        return true;
+    }
+
     /**
      * @hide
      */
@@ -5661,11 +5707,13 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
         return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String procStateToString(int procState) {
         final String procStateStr;
         switch (procState) {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 111895e..1edf4bd 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1974,8 +1974,8 @@
             binder = new Binder(descriptor);
         }
 
-        private LaunchCookie(Parcel in) {
-            this.binder = in.readStrongBinder();
+        private LaunchCookie(IBinder binder) {
+            this.binder = binder;
         }
 
         /** @hide */
@@ -1996,7 +1996,11 @@
 
         /** @hide */
         public static LaunchCookie readFromParcel(@NonNull Parcel in) {
-            return new LaunchCookie(in);
+            IBinder binder = in.readStrongBinder();
+            if (binder == null) {
+                return null;
+            }
+            return new LaunchCookie(binder);
         }
 
         /** @hide */
@@ -2017,7 +2021,7 @@
 
                     @Override
                     public LaunchCookie createFromParcel(Parcel source) {
-                        return new LaunchCookie(source);
+                        return LaunchCookie.readFromParcel(source);
                     }
 
                     @Override
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5d2a26e..2c00c99 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -303,7 +303,7 @@
     public static final boolean DEBUG_MEMORY_TRIM = false;
     private static final boolean DEBUG_PROVIDER = false;
     public static final boolean DEBUG_ORDER = false;
-    private static final boolean DEBUG_APP_INFO = true;
+    private static final boolean DEBUG_APP_INFO = false;
     private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
     /**
      * The delay to release the provider when it has no more references. It reduces the number of
@@ -580,6 +580,7 @@
         public IBinder shareableActivityToken;
         // The token of the TaskFragment that embedded this activity.
         @Nullable public IBinder mTaskFragmentToken;
+        public IBinder initialCallerInfoAccessToken;
         int ident;
         @UnsupportedAppUsage
         Intent intent;
@@ -668,7 +669,7 @@
                 List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo,
                 boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
                 IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
-                IBinder taskFragmentToken) {
+                IBinder taskFragmentToken, IBinder initialCallerInfoAccessToken) {
             this.token = token;
             this.assistToken = assistToken;
             this.shareableActivityToken = shareableActivityToken;
@@ -685,6 +686,7 @@
             this.profilerInfo = profilerInfo;
             this.overrideConfig = overrideConfig;
             this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
+            this.initialCallerInfoAccessToken = initialCallerInfoAccessToken;
             mSceneTransitionInfo = sceneTransitionInfo;
             mLaunchedFromBubble = launchedFromBubble;
             mTaskFragmentToken = taskFragmentToken;
@@ -3914,7 +3916,7 @@
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
                         r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
-                        r.assistToken, r.shareableActivityToken);
+                        r.assistToken, r.shareableActivityToken, r.initialCallerInfoAccessToken);
 
                 if (customIntent != null) {
                     activity.mIntent = customIntent;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7907059..5074ed7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1550,7 +1550,7 @@
             AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
 
     /**
-     * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+     * Allows an app with a major use case of backing-up or syncing content to run longer jobs.
      *
      * @hide
      */
@@ -4588,19 +4588,19 @@
          */
         private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
                 @UidState int toUidState, @OpFlags int flags) {
-            NoteOpEvent lastAccessEvent = null;
+            NoteOpEvent lastRejectEvent = null;
             for (AttributedOpEntry attributionEntry : mAttributedOpEntries.values()) {
-                NoteOpEvent lastAttributionAccessEvent = attributionEntry.getLastRejectEvent(
+                NoteOpEvent lastAttributionRejectEvent = attributionEntry.getLastRejectEvent(
                         fromUidState, toUidState, flags);
 
-                if (lastAccessEvent == null || (lastAttributionAccessEvent != null
-                        && lastAttributionAccessEvent.getNoteTime()
-                        > lastAccessEvent.getNoteTime())) {
-                    lastAccessEvent = lastAttributionAccessEvent;
+                if (lastRejectEvent == null || (lastAttributionRejectEvent != null
+                        && lastAttributionRejectEvent.getNoteTime()
+                        > lastRejectEvent.getNoteTime())) {
+                    lastRejectEvent = lastAttributionRejectEvent;
                 }
             }
 
-            return lastAccessEvent;
+            return lastRejectEvent;
         }
 
         /**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4f1db7d..d8aded40 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1269,6 +1269,22 @@
         return appMetadata != null ? appMetadata : new PersistableBundle();
     }
 
+    @Override
+    public @AppMetadataSource int getAppMetadataSource(@NonNull String packageName)
+            throws NameNotFoundException {
+        Objects.requireNonNull(packageName, "packageName cannot be null");
+        int source = PackageManager.APP_METADATA_SOURCE_UNKNOWN;
+        try {
+            source = mPM.getAppMetadataSource(packageName, getUserId());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(NameNotFoundException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return source;
+    }
+
     @SuppressWarnings("unchecked")
     @Override
     public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java
new file mode 100644
index 0000000..583408e
--- /dev/null
+++ b/core/java/android/app/ComponentCaller.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Process;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Represents the app that launched the component. See below for the APIs available on the component
+ * caller.
+ *
+ * <p><b>Note</b>, that in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+ * {@link Activity} has access to {@link ComponentCaller} instances.
+ *
+ * @see Activity#getInitialCaller()
+ */
+@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+public final class ComponentCaller {
+    private final IBinder mActivityToken;
+    private final IBinder mCallerToken;
+
+    public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) {
+        mActivityToken = activityToken;
+        mCallerToken = callerToken;
+    }
+
+    /**
+     * Returns the uid of this component caller.
+     *
+     * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+     * {@link Activity} has access to {@link ComponentCaller} instances.
+     * <p>
+     * <h3>Requirements for {@link Activity} callers</h3>
+     *
+     * <p>In order to receive the calling app's uid, at least one of the following has to be met:
+     * <ul>
+     *     <li>The calling app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)}
+     *     with a value of {@code true} and launch this activity with the resulting
+     *     {@code ActivityOptions}.
+     *     <li>The launched activity has the same uid as the calling app.
+     *     <li>The launched activity is running in a package that is signed with the same key used
+     *     to sign the platform (typically only system packages such as Settings will meet this
+     *     requirement).
+     * </ul>
+     * These are the same requirements for {@link #getPackage()}; if any of these are met, then
+     * these methods can be used to obtain the uid and package name of the calling app. If none are
+     * met, then {@link Process#INVALID_UID} is returned.
+     *
+     * <p>Note, even if the above conditions are not met, the calling app's identity may still be
+     * available from {@link Activity#getCallingPackage()} if this activity was started with
+     * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+     *
+     * @return the uid of the calling app or {@link Process#INVALID_UID} if the current component
+     * cannot access the identity of the calling app or the caller is invalid
+     *
+     * @see ActivityOptions#setShareIdentityEnabled(boolean)
+     * @see Activity#getLaunchedFromUid()
+     */
+    public int getUid() {
+        return ActivityClient.getInstance().getLaunchedFromUid(mActivityToken);
+    }
+
+    /**
+     * Returns the package name of this component caller.
+     *
+     * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+     * {@link Activity} has access to {@link ComponentCaller} instances.
+     * <p>
+     * <h3>Requirements for {@link Activity} callers</h3>
+     *
+     * <p>In order to receive the calling app's package name, at least one of the following has to
+     * be met:
+     * <ul>
+     *     <li>The calling app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)}
+     *     with a value of {@code true} and launch this activity with the resulting
+     *     {@code ActivityOptions}.
+     *     <li>The launched activity has the same uid as the calling app.
+     *     <li>The launched activity is running in a package that is signed with the same key used
+     *     to sign the platform (typically only system packages such as Settings will meet this
+     *     meet this requirement).
+     * </ul>
+     * These are the same requirements for {@link #getUid()}; if any of these are met, then these
+     * methods can be used to obtain the uid and package name of the calling app. If none are met,
+     * then {@code null} is returned.
+     *
+     * <p>Note, even if the above conditions are not met, the calling app's identity may still be
+     * available from {@link Activity#getCallingPackage()} if this activity was started with
+     * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+     *
+     * @return the package name of the calling app or null if the current component cannot access
+     * the identity of the calling app or the caller is invalid
+     *
+     * @see ActivityOptions#setShareIdentityEnabled(boolean)
+     * @see Activity#getLaunchedFromPackage()
+     */
+    @Nullable
+    public String getPackage() {
+        return ActivityClient.getInstance().getLaunchedFromPackage(mActivityToken);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (obj == null || !(obj instanceof ComponentCaller other)) {
+            return false;
+        }
+        return this.mActivityToken == other.mActivityToken
+                && this.mCallerToken == other.mCallerToken;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mActivityToken);
+        result = 31 * result + Objects.hashCode(mCallerToken);
+        return result;
+    }
+}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d540748..e2e2f1d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -225,7 +225,7 @@
             boolean focused, boolean newSessionId);
     boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
             in IBinder activityToken, int flags);
-    boolean isAssistDataAllowedOnCurrentActivity();
+    boolean isAssistDataAllowed();
     boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
             in String callingPackageName, String callingAttributionTag);
 
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 68512b8..454d605 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1423,8 +1423,8 @@
                 info, title, parent, id,
                 (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                 new Configuration(), null /* referrer */, null /* voiceInteractor */,
-                null /* window */, null /* activityCallback */, null /*assistToken*/,
-                null /*shareableActivityToken*/);
+                null /* window */, null /* activityCallback */, null /* assistToken */,
+                null /* shareableActivityToken */, null /* initialCallerInfoAccessToken */);
         return activity;
     }
 
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 8b8576a..b5e5074 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -721,28 +721,28 @@
     /**
      * Returns whether the device is currently locked for the user.
      * <p>
-     * This returns the device locked state for the {@link Context}'s user. If this user is the
-     * current user, then the device is considered "locked" when the lock screen is showing (i.e.
-     * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
-     * swipe), and the user has a PIN, pattern, or password.
+     * This method returns the device locked state for the {@link Context}'s user. The device is
+     * considered to be locked for a user when the user's apps are currently inaccessible and some
+     * form of lock screen authentication is required to regain access to them. The lock screen
+     * authentication typically uses PIN, pattern, password, or biometric. Some devices may support
+     * additional methods, such as unlock using a paired smartwatch. "Swipe" does not count as
+     * authentication; if the lock screen is dismissible with swipe, for example due to the lock
+     * screen being set to Swipe or due to the device being kept unlocked by being near a trusted
+     * bluetooth device or in a trusted location, the device is considered unlocked.
+     * <div class="note">
      * <p>
-     * Note: the above definition implies that a user with no PIN, pattern, or password is never
-     * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
-     * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
-     * authentication has just completed or a trust agent is keeping the device unlocked, since in
-     * these cases the lock screen is dismissible with swipe.
+     * <b>Note:</b> In the case of multiple full users, each user can have their own lock screen
+     * authentication configured. The device-locked state may differ between different users. For
+     * example, the device may be unlocked for the current user, but locked for a non-current user
+     * if lock screen authentication would be required to access that user's apps after switching to
+     * that user.
      * <p>
-     * For a user that is not the current user but can be switched to (usually this means "another
-     * full user"), and that has a PIN, pattern, or password, the device is always considered
-     * locked.
-     * <p>
-     * For a profile with a unified challenge, the device locked state is the same as that of the
-     * parent user.
-     * <p>
-     * For a profile with a separate challenge, the device becomes unlocked when the profile's PIN,
-     * pattern, password, or biometric is verified. It becomes locked when the parent user becomes
-     * locked, the screen turns off, the device reboots, the device policy controller locks the
-     * profile, or the timeout set by the device policy controller expires.
+     * In the case of a profile, when the device goes to the main lock screen, up to two layers of
+     * authentication may be required to regain access to the profile's apps: one to unlock the main
+     * lock screen, and one to unlock the profile (when a separate profile challenge is required).
+     * For a profile, the device is considered to be locked as long as any challenge remains, either
+     * the parent user's challenge (when applicable) or the profile's challenge (when applicable).
+     * </div>
      *
      * @return {@code true} if the device is currently locked for the user
      * @see #isKeyguardLocked()
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d705eeb..0a34d36 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4649,13 +4649,24 @@
          * to turn it off and use a normal notification, as this can be extremely
          * disruptive.
          *
-         * <p>
-         * The system UI may choose to display a heads-up notification, instead of
-         * launching this intent, while the user is using the device.
-         * </p>
          * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
          * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
-         * use full screen intents.</p>
+         * use full screen intents. </p>
+         * <p>
+         * Prior to {@link Build.VERSION_CODES#TIRAMISU}, the system may display a
+         * heads up notification (which may display on screen longer than other heads up
+         * notifications), instead of launching the intent, while the user is using the device.
+         * From {@link Build.VERSION_CODES#TIRAMISU},
+         * the system UI will display a heads up notification, instead of launching this intent,
+         * while the user is using the device. This notification will display with emphasized
+         * action buttons. If the posting app holds
+         * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the heads
+         * up notification will appear persistently until the user dismisses or snoozes it, or
+         * the app cancels it. If the posting app does not hold
+         * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the notification will
+         * appear as heads up notification even when the screen is locked or turned off, and this
+         * notification will only be persistent for 60 seconds.
+         * </p>
          * <p>
          * To be launched as a full screen intent, the notification must also be posted to a
          * channel with importance level set to IMPORTANCE_HIGH or higher.
@@ -5946,6 +5957,12 @@
                 // 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 (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                        Log.d(TAG, "setting evenly divided mode on action list");
+                    }
+                    big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+                }
             }
             big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
             if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6438,15 @@
                     // Remove full-length color spans and ensure text contrast with the button fill.
                     title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                 }
-                button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
+                final CharSequence label = ensureColorSpanContrast(title, p);
+                if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                        Log.d(TAG, "new action layout enabled, gluing instead of setting text");
+                    }
+                    button.setCharSequence(R.id.action0, "glueLabel", label);
+                } else {
+                    button.setTextViewText(R.id.action0, label);
+                }
                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                         buttonFillColor, mInNightMode);
                 if (tombstone) {
@@ -6438,7 +6463,14 @@
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(buttonFillColor));
                 if (p.mCallStyleActions) {
-                    button.setImageViewIcon(R.id.action0, action.getIcon());
+                    if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                        if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                            Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
+                        }
+                        button.setIcon(R.id.action0, "glueIcon", action.getIcon());
+                    } else {
+                        button.setImageViewIcon(R.id.action0, action.getIcon());
+                    }
                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
                     button.setBoolean(R.id.action0, "setIsPriority", priority);
                     int minWidthDimen =
@@ -9565,6 +9597,15 @@
      * </pre>
      */
     public static class CallStyle extends Style {
+        /**
+         * @hide
+         */
+        public static final boolean USE_NEW_ACTION_LAYOUT = false;
+
+        /**
+         * @hide
+         */
+        public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
 
         /**
          * @hide
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 15d692a..ab5395e 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,6 +15,7 @@
  */
 package android.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -30,6 +31,9 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.VibrationEffect;
+import android.os.vibrator.persistence.VibrationXmlParser;
+import android.os.vibrator.persistence.VibrationXmlSerializer;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
@@ -49,6 +53,8 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -146,6 +152,7 @@
     private static final String ATT_LIGHTS = "lights";
     private static final String ATT_LIGHT_COLOR = "light_color";
     private static final String ATT_VIBRATION = "vibration";
+    private static final String ATT_VIBRATION_EFFECT = "vibration_effect";
     private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
     private static final String ATT_SOUND = "sound";
     private static final String ATT_USAGE = "usage";
@@ -253,7 +260,8 @@
     private boolean mSoundRestored = false;
     private boolean mLights;
     private int mLightColor = DEFAULT_LIGHT_COLOR;
-    private long[] mVibration;
+    private long[] mVibrationPattern;
+    private VibrationEffect mVibrationEffect;
     // Bitwise representation of fields that have been changed by the user, preventing the app from
     // making changes to these fields.
     private int mUserLockedFields;
@@ -324,9 +332,13 @@
             mSound = null;
         }
         mLights = in.readByte() != 0;
-        mVibration = in.createLongArray();
-        if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) {
-            mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
+        mVibrationPattern = in.createLongArray();
+        if (mVibrationPattern != null && mVibrationPattern.length > MAX_VIBRATION_LENGTH) {
+            mVibrationPattern = Arrays.copyOf(mVibrationPattern, MAX_VIBRATION_LENGTH);
+        }
+        if (Flags.notificationChannelVibrationEffectApi()) {
+            mVibrationEffect =
+                    in.readInt() != 0 ? VibrationEffect.CREATOR.createFromParcel(in) : null;
         }
         mUserLockedFields = in.readInt();
         mUserVisibleTaskShown = in.readByte() != 0;
@@ -381,7 +393,15 @@
             dest.writeByte((byte) 0);
         }
         dest.writeByte(mLights ? (byte) 1 : (byte) 0);
-        dest.writeLongArray(mVibration);
+        dest.writeLongArray(mVibrationPattern);
+        if (Flags.notificationChannelVibrationEffectApi()) {
+            if (mVibrationEffect != null) {
+                dest.writeInt(1);
+                mVibrationEffect.writeToParcel(dest, /* flags= */ 0);
+            } else {
+                dest.writeInt(0);
+            }
+        }
         dest.writeInt(mUserLockedFields);
         dest.writeByte(mUserVisibleTaskShown ? (byte) 1 : (byte) 0);
         dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
@@ -585,8 +605,8 @@
 
     /**
      * Sets the vibration pattern for notifications posted to this channel. If the provided
-     * pattern is valid (non-null, non-empty), will enable vibration on this channel
-     * (equivalent to calling {@link #enableVibration(boolean)} with {@code true}).
+     * pattern is valid (non-null, non-empty with at least 1 non-zero value), will enable vibration
+     * on this channel (equivalent to calling {@link #enableVibration(boolean)} with {@code true}).
      * Otherwise, vibration will be disabled unless {@link #enableVibration(boolean)} is
      * used with {@code true}, in which case the default vibration will be used.
      *
@@ -595,7 +615,56 @@
      */
     public void setVibrationPattern(long[] vibrationPattern) {
         this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
-        this.mVibration = vibrationPattern;
+        this.mVibrationPattern = vibrationPattern;
+        if (Flags.notificationChannelVibrationEffectApi()) {
+            try {
+                this.mVibrationEffect =
+                        VibrationEffect.createWaveform(vibrationPattern, /* repeat= */ -1);
+            } catch (IllegalArgumentException | NullPointerException e) {
+                this.mVibrationEffect = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a {@link VibrationEffect} for notifications posted to this channel. If the
+     * provided effect is non-null, will enable vibration on this channel (equivalent
+     * to calling {@link #enableVibration(boolean)} with {@code true}). Otherwise
+     * vibration will be disabled unless {@link #enableVibration(boolean)} is used with
+     * {@code true}, in which case the default vibration will be used.
+     *
+     * <p>The effect passed here will be returned from {@link #getVibrationEffect()}.
+     * If the provided {@link VibrationEffect} is an equivalent to a wave-form
+     * vibration pattern, the equivalent wave-form pattern will be returned from
+     * {@link #getVibrationPattern()}.
+     *
+     * <p>Note that some {@link VibrationEffect}s may not be playable on some devices.
+     * In cases where such an effect is passed here, vibration will still be enabled
+     * for the channel, but the default vibration will be used. Nonetheless, the
+     * provided effect will be stored and be returned from {@link #getVibrationEffect}
+     * calls, and could be used by the same channel on a different device, for example,
+     * in cases the user backs up and restores to a device that does have the ability
+     * to play the effect, where that effect will be used instead of the default. To
+     * avoid such issues that could make the vibration behavior of your notification
+     * channel differ among different devices, it's recommended that you avoid
+     * vibration effect primitives, as the support for them differs widely among
+     * devices (read {@link VibrationEffect.Composition} for more on vibration
+     * primitives).
+     *
+     * <p>Only modifiable before the channel is submitted to
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+     *
+     * @see #getVibrationEffect()
+     * @see Vibrator#areEffectsSupported(int...)
+     * @see Vibrator#arePrimitivesSupported(int...)
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API)
+    public void setVibrationEffect(@Nullable VibrationEffect effect) {
+        this.mVibrationEnabled = effect != null;
+        this.mVibrationEffect = effect;
+        this.mVibrationPattern =
+                effect == null
+                ? null : effect.computeCreateWaveformOffOnTimingsOrNull();
     }
 
     /**
@@ -768,7 +837,35 @@
      * vibration is not enabled ({@link #shouldVibrate()}).
      */
     public long[] getVibrationPattern() {
-        return mVibration;
+        return mVibrationPattern;
+    }
+
+    /**
+     * Returns the {@link VibrationEffect} for notifications posted to this channel.
+     * The returned effect is derived from either the effect provided in the
+     * {@link #setVibrationEffect(VibrationEffect)} method, or the equivalent vibration effect
+     * of the pattern set via the {@link #setVibrationPattern(long[])} method, based on setter
+     * method that was called last.
+     *
+     * The returned effect will be ignored in one of the following cases:
+     * <ul>
+     *   <li> vibration is not enabled for the channel (i.e. {@link #shouldVibrate()}
+     *        returns {@code false}).
+     *   <li> the effect is not supported/playable by the device. In this case, if
+     *        vibration is enabled for the channel, the default channel vibration will
+     *        be used instead.
+     * </ul>
+     *
+     * @return the {@link VibrationEffect} set via {@link
+     *         #setVibrationEffect(VibrationEffect)}, or the equivalent of the
+     *         vibration set via {@link #setVibrationPattern(long[])}.
+     *
+     *  @see VibrationEffect#createWaveform(long[], int)
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API)
+    @Nullable
+    public VibrationEffect getVibrationEffect() {
+        return mVibrationEffect;
     }
 
     /**
@@ -991,7 +1088,19 @@
 
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
+        // Set the pattern before the effect, so that we can properly handle cases where the pattern
+        // is null, but the effect is not null (i.e. for non-waveform VibrationEffects - the ones
+        // which cannot be represented as a vibration pattern).
         setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
+        if (Flags.notificationChannelVibrationEffectApi()) {
+            VibrationEffect vibrationEffect = safeVibrationEffect(parser, ATT_VIBRATION_EFFECT);
+            if (vibrationEffect != null) {
+                // Restore the effect only if it is not null. This allows to avoid undoing a
+                // `setVibrationPattern` call above, if that was done with a non-null pattern
+                // (e.g. back up from a version that did not support `setVibrationEffect`).
+                setVibrationEffect(vibrationEffect);
+            }
+        }
         enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
         setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
         setDeleted(safeBool(parser, ATT_DELETED, false));
@@ -1180,6 +1289,9 @@
         if (getVibrationPattern() != null) {
             out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern()));
         }
+        if (getVibrationEffect() != null) {
+            out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));
+        }
         if (getUserLockedFields() != 0) {
             out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields());
         }
@@ -1260,6 +1372,9 @@
         record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
         record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isUserVisibleTaskShown()));
         record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
+        if (getVibrationEffect() != null) {
+            record.put(ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));
+        }
         record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
         record.put(ATT_DELETED, Boolean.toString(isDeleted()));
         record.put(ATT_DELETED_TIME_MS, Long.toString(getDeletedTimeMs()));
@@ -1287,6 +1402,30 @@
         return val == null ? null : Uri.parse(val);
     }
 
+    private static String vibrationToString(VibrationEffect effect) {
+        StringWriter writer = new StringWriter();
+        try {
+            VibrationXmlSerializer.serialize(
+                    effect, writer, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to serialize vibration: " + effect, e);
+        }
+        return writer.toString();
+    }
+
+    private static VibrationEffect safeVibrationEffect(TypedXmlPullParser parser, String att) {
+        final String val = parser.getAttributeValue(null, att);
+        if (val != null) {
+            try {
+                return VibrationXmlParser.parseVibrationEffect(
+                        new StringReader(val), VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to read serialized vibration effect", e);
+            }
+        }
+        return null;
+    }
+
     private static int safeInt(TypedXmlPullParser parser, String att, int defValue) {
         return parser.getAttributeInt(null, att, defValue);
     }
@@ -1361,7 +1500,8 @@
                 && Objects.equals(getName(), that.getName())
                 && Objects.equals(mDesc, that.mDesc)
                 && Objects.equals(getSound(), that.getSound())
-                && Arrays.equals(mVibration, that.mVibration)
+                && Arrays.equals(mVibrationPattern, that.mVibrationPattern)
+                && Objects.equals(getVibrationEffect(), that.getVibrationEffect())
                 && Objects.equals(getGroup(), that.getGroup())
                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
                 && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
@@ -1379,9 +1519,9 @@
                 getUserLockedFields(), isUserVisibleTaskShown(),
                 mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
                 getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
-                mImportanceLockedDefaultApp, mOriginalImportance,
+                mImportanceLockedDefaultApp, mOriginalImportance, getVibrationEffect(),
                 mParentId, mConversationId, mDemoted, mImportantConvo);
-        result = 31 * result + Arrays.hashCode(mVibration);
+        result = 31 * result + Arrays.hashCode(mVibrationPattern);
         return result;
     }
 
@@ -1413,7 +1553,9 @@
                 + ", mSound=" + mSound
                 + ", mLights=" + mLights
                 + ", mLightColor=" + mLightColor
-                + ", mVibration=" + Arrays.toString(mVibration)
+                + ", mVibrationPattern=" + Arrays.toString(mVibrationPattern)
+                + ", mVibrationEffect="
+                        + (mVibrationEffect == null ? "null" : mVibrationEffect.toString())
                 + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
                 + ", mUserVisibleTaskShown=" + mUserVisibleTaskShown
                 + ", mVibrationEnabled=" + mVibrationEnabled
@@ -1448,8 +1590,8 @@
         }
         proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
         proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
-        if (mVibration != null) {
-            for (long v : mVibration) {
+        if (mVibrationPattern != null) {
+            for (long v : mVibrationPattern) {
                 proto.write(NotificationChannelProto.VIBRATION, v);
             }
         }
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index edf0a46..6a114f9 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -114,6 +114,22 @@
     }
 
     /**
+     * Remove all Messages from the Handler with the given code.
+     *
+     * This method intentionally avoids creating the Handler if it doesn't
+     * already exist.
+     */
+    private static void handlerRemoveMessages(int what) {
+        synchronized (sLock) {
+            if (sHandler == null) {
+                // Nothing to remove
+                return;
+            }
+            getHandler().removeMessages(what);
+        }
+    }
+
+    /**
      * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}.
      *
      * Used by SharedPreferences$Editor#startCommit().
@@ -156,17 +172,13 @@
         long startTime = System.currentTimeMillis();
         boolean hadMessages = false;
 
-        Handler handler = getHandler();
-
         synchronized (sLock) {
-            if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
-                // Delayed work will be processed at processPendingWork() below
-                handler.removeMessages(QueuedWorkHandler.MSG_RUN);
-
-                if (DEBUG) {
-                    hadMessages = true;
-                    Log.d(LOG_TAG, "waiting");
-                }
+            if (DEBUG) {
+                hadMessages = getHandler().hasMessages(QueuedWorkHandler.MSG_RUN);
+            }
+            handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
+            if (DEBUG && hadMessages) {
+                Log.d(LOG_TAG, "waiting");
             }
 
             // We should not delay any work as this might delay the finishers
@@ -257,7 +269,7 @@
                 sWork = new LinkedList<>();
 
                 // Remove all msg-s as all work will be processed now
-                getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
+                handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
             }
 
             if (work.size() > 0) {
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a271328..a045eae 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.Flags.enableNightModeCache;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -31,6 +33,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Binder;
+import android.os.IpcDataCache;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -874,6 +877,51 @@
         }
     }
 
+    private Integer getNightModeFromServer() {
+        try {
+            if (sGlobals != null) {
+                return sGlobals.mService.getNightMode();
+            }
+            return -1;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Retrieve the night mode for the user.
+     */
+    private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery =
+            new IpcDataCache.QueryHandler<>() {
+
+                @Override
+                @NonNull
+                public Integer apply(Void query) {
+                    return getNightModeFromServer();
+                }
+            };
+
+    private static final String NIGHT_MODE_API = "getNightMode";
+
+    /**
+     * Cache the night mode for a user.
+     */
+    private final IpcDataCache<Void, Integer> mNightModeCache =
+            new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+                    NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery);
+
+    /**
+     * Invalidate the night mode cache.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+    public static void invalidateNightModeCache() {
+        IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+                NIGHT_MODE_API);
+    }
+
     /**
      * Returns the currently configured night mode.
      * <p>
@@ -890,14 +938,11 @@
      * @see #setNightMode(int)
      */
     public @NightMode int getNightMode() {
-        if (sGlobals != null) {
-            try {
-                return sGlobals.mService.getNightMode();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+        if (enableNightModeCache()) {
+            return mNightModeCache.query(null);
+        } else {
+            return getNightModeFromServer();
         }
-        return -1;
     }
 
     /**
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index ea31ef3..112c5fd 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -26,6 +26,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.server.backup.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
@@ -56,7 +58,7 @@
      *
      * @hide
      */
-    public static final int DATA_TYPES_ALLOWED = 15;
+    public static final int DATA_TYPES_ALLOWED = 150;
 
     /**
      * Denotes that the annotated element identifies a data type as required by the logging methods
@@ -299,7 +301,7 @@
         }
 
         if (!mResults.containsKey(dataType)) {
-            if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+            if (mResults.keySet().size() == getDataTypesAllowed()) {
                 // This is a new data type and we're already at capacity.
                 Slog.d(TAG, "Logger is full, ignoring new data type");
                 return null;
@@ -315,6 +317,14 @@
         return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
     }
 
+    private int getDataTypesAllowed(){
+        if (Flags.enableIncreaseDatatypesForAgentLogging()) {
+            return DATA_TYPES_ALLOWED;
+        } else {
+            return 15;
+        }
+    }
+
     /**
      * Encapsulate logging results for a single data type.
      */
diff --git a/core/java/android/app/contextualsearch/OWNERS b/core/java/android/app/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/core/java/android/app/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index a5d4a14..c40b23e 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -50,3 +50,10 @@
   description: "Adds a new voicemail category for notifications"
   bug: "322806700"
 }
+
+flag {
+  name: "notification_channel_vibration_effect_api"
+  namespace: "systemui"
+  description: "This flag enables the API to allow setting VibrationEffect for NotificationChannels"
+  bug: "241732519"
+}
\ No newline at end of file
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 5e55268..6357a20 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -298,6 +298,33 @@
         return result;
     }
 
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("ClientTransaction{");
+        if (mTransactionItems != null) {
+            // #addTransactionItem
+            sb.append("\n  transactionItems=[");
+            final int size = mTransactionItems.size();
+            for (int i = 0; i < size; i++) {
+                sb.append("\n    ").append(mTransactionItems.get(i));
+            }
+            sb.append("\n  ]");
+        } else {
+            // #addCallback
+            sb.append("\n  callbacks=[");
+            final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
+            for (int i = 0; i < size; i++) {
+                sb.append("\n    ").append(mActivityCallbacks.get(i));
+            }
+            sb.append("\n  ]");
+            // #setLifecycleStateRequest
+            sb.append("\n  stateRequest=").append(mLifecycleStateRequest);
+        }
+        sb.append("\n}");
+        return sb.toString();
+    }
+
     /** Dump transaction items callback items and final lifecycle state request. */
     void dump(@NonNull String prefix, @NonNull PrintWriter pw,
             @NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 4d53701..95f5ad0 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -80,6 +80,7 @@
     private IBinder mShareableActivityToken;
     private boolean mLaunchedFromBubble;
     private IBinder mTaskFragmentToken;
+    private IBinder mInitialCallerInfoAccessToken;
     /**
      * It is only non-null if the process is the first time to launch activity. It is only an
      * optimization for quick look up of the interface so the field is ignored for comparison.
@@ -106,7 +107,7 @@
                 mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
                 mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward,
                 mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
-                mTaskFragmentToken);
+                mTaskFragmentToken, mInitialCallerInfoAccessToken);
         client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -140,7 +141,7 @@
             boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
             @Nullable IActivityClientController activityClientController,
             @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
-            @Nullable IBinder taskFragmentToken) {
+            @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken) {
         LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
         if (instance == null) {
             instance = new LaunchActivityItem();
@@ -155,7 +156,7 @@
                 sceneTransitionInfo, isForward,
                 profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
                 assistToken, activityClientController, shareableActivityToken,
-                launchedFromBubble, taskFragmentToken);
+                launchedFromBubble, taskFragmentToken, initialCallerInfoAccessToken);
 
         return instance;
     }
@@ -170,7 +171,7 @@
     @Override
     public void recycle() {
         setValues(this, null, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
-                null, false, null, null, null, null, false, null);
+                null, false, null, null, null, null, false, null, null);
         ObjectPool.recycle(this);
     }
 
@@ -201,6 +202,7 @@
         dest.writeStrongBinder(mShareableActivityToken);
         dest.writeBoolean(mLaunchedFromBubble);
         dest.writeStrongBinder(mTaskFragmentToken);
+        dest.writeStrongBinder(mInitialCallerInfoAccessToken);
     }
 
     /** Read from Parcel. */
@@ -220,6 +222,7 @@
                 IActivityClientController.Stub.asInterface(in.readStrongBinder()),
                 in.readStrongBinder(),
                 in.readBoolean(),
+                in.readStrongBinder(),
                 in.readStrongBinder());
     }
 
@@ -259,7 +262,9 @@
                 && Objects.equals(mProfilerInfo, other.mProfilerInfo)
                 && Objects.equals(mAssistToken, other.mAssistToken)
                 && Objects.equals(mShareableActivityToken, other.mShareableActivityToken)
-                && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken);
+                && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken)
+                && Objects.equals(mInitialCallerInfoAccessToken,
+                        other.mInitialCallerInfoAccessToken);
     }
 
     @Override
@@ -283,6 +288,7 @@
         result = 31 * result + Objects.hashCode(mAssistToken);
         result = 31 * result + Objects.hashCode(mShareableActivityToken);
         result = 31 * result + Objects.hashCode(mTaskFragmentToken);
+        result = 31 * result + Objects.hashCode(mInitialCallerInfoAccessToken);
         return result;
     }
 
@@ -345,7 +351,7 @@
             @Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
             @Nullable IActivityClientController activityClientController,
             @Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
-            @Nullable IBinder taskFragmentToken) {
+            @Nullable IBinder taskFragmentToken, @Nullable IBinder initialCallerInfoAccessToken) {
         instance.mActivityToken = activityToken;
         instance.mIntent = intent;
         instance.mIdent = ident;
@@ -368,5 +374,6 @@
         instance.mShareableActivityToken = shareableActivityToken;
         instance.mLaunchedFromBubble = launchedFromBubble;
         instance.mTaskFragmentToken = taskFragmentToken;
+        instance.mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index ba94077..406e00a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -97,6 +97,10 @@
                 executeCallbacks(transaction);
                 executeLifecycleState(transaction);
             }
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to execute the transaction: "
+                    + transactionToString(transaction, mTransactionHandler));
+            throw e;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
new file mode 100644
index 0000000..1ae5264
--- /dev/null
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+     namespace: "system_performance"
+     name: "enable_night_mode_cache"
+     description: "Enables the use of binder caching for system night mode."
+     bug: "255999432"
+}
\ No newline at end of file
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 87d97d5..7bfaef4 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -40,28 +40,79 @@
     /** @hide */ public long apkBytes;
     /** @hide */ public long libBytes;
     /** @hide */ public long dmBytes;
+    /** @hide */ public long dexoptBytes;
+    /** @hide */ public long curProfBytes;
+    /** @hide */ public long refProfBytes;
     /** @hide */ public long externalCacheBytes;
 
-    /** Represents all .apk files in application code path.
+    /**
+     * Represents all nonstale dexopt and runtime artifacts of application.
+     * This includes AOT-compiled code and other data that can speed up app execution.
+     * For more detailed information, read the
+     * <a href="https://source.android.com/docs/core/runtime/jit-compiler#flow">JIT compiler</a>
+     * guide.
+     *
+     * Dexopt artifacts become stale when one of their dependencies
+     * has changed. They may be cleaned up or replaced by ART Services at any time.
+     *
+     * For a preload app, this type includes dexopt artifacts on readonly partitions
+     * if they are up-to-date.
+     *
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the sum of sizes for files of this type. The sum might include the size of data
+     * that is part of appBytes, dataBytes or cacheBytes.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0;
+
+    /**
+     * Represents reference profile of application.
+     *
+     * Reference profiles are the ones used during the last profile-guided dexopt.
+     * If the last dexopt wasn't profile-guided, then these profiles were not used.
+     *
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the size of files of this type.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1;
+
+    /**
+     * Represents current profile of application.
+     *
+     * Current profiles may or may not be used during the next profile-guided dexopt.
+     *
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the size of files of this type. This size fluctuates regularly,
+     * it goes up when the user uses more and more classes/methods and comes down when
+     * a deamon merges this into the ref profile and does profile-guided dexopt.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2;
+
+    /**
+     * Represents all .apk files in application code path.
      * Can be used as an input to {@link #getAppBytesByDataType(int)}
      * to get the sum of sizes for files of this type.
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
-    public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+    public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3;
 
-    /** Represents all .dm files in application code path.
+    /**
+     * Represents all .dm files in application code path.
      * Can be used as an input to {@link #getAppBytesByDataType(int)}
      * to get the sum of sizes for files of this type.
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
-    public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+    public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4;
 
-    /** Represents lib/ in application code path.
+    /**
+     * Represents lib/ in application code path.
      * Can be used as an input to {@link #getAppBytesByDataType(int)}
      * to get the size of lib/ directory.
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
-    public static final int APP_DATA_TYPE_LIB = 2;
+    public static final int APP_DATA_TYPE_LIB = 5;
 
     /**
      * Keep in sync with the file types defined above.
@@ -69,6 +120,9 @@
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
     @IntDef(flag = false, value = {
+        APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT,
+        APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE,
+        APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE,
         APP_DATA_TYPE_FILE_TYPE_APK,
         APP_DATA_TYPE_FILE_TYPE_DM,
         APP_DATA_TYPE_LIB,
@@ -103,6 +157,9 @@
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
     public long getAppBytesByDataType(@AppDataType int dataType) {
         switch (dataType) {
+          case APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT: return dexoptBytes;
+          case APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE: return refProfBytes;
+          case APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE: return curProfBytes;
           case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
           case APP_DATA_TYPE_LIB: return libBytes;
           case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
@@ -161,6 +218,9 @@
         this.codeBytes = in.readLong();
         this.dataBytes = in.readLong();
         this.cacheBytes = in.readLong();
+        this.dexoptBytes = in.readLong();
+        this.refProfBytes = in.readLong();
+        this.curProfBytes = in.readLong();
         this.apkBytes = in.readLong();
         this.libBytes = in.readLong();
         this.dmBytes = in.readLong();
@@ -177,6 +237,9 @@
         dest.writeLong(codeBytes);
         dest.writeLong(dataBytes);
         dest.writeLong(cacheBytes);
+        dest.writeLong(dexoptBytes);
+        dest.writeLong(refProfBytes);
+        dest.writeLong(curProfBytes);
         dest.writeLong(apkBytes);
         dest.writeLong(libBytes);
         dest.writeLong(dmBytes);
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index f1ca086..eca0039 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -99,6 +100,13 @@
      */
     public static final int STATUS_ACCESS_DENIED = 5;
 
+    /**
+     * The value of the status code that indicates the method called is not supported by the
+     * implementation of {@link WearableSensingService}.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
+    public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_UNKNOWN,
@@ -106,7 +114,8 @@
             STATUS_UNSUPPORTED,
             STATUS_SERVICE_UNAVAILABLE,
             STATUS_WEARABLE_UNAVAILABLE,
-            STATUS_ACCESS_DENIED
+            STATUS_ACCESS_DENIED,
+            STATUS_UNSUPPORTED_OPERATION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StatusCode {}
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
new file mode 100644
index 0000000..5e8bdb5
--- /dev/null
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -0,0 +1,29 @@
+package: "android.app.wearable"
+
+flag {
+    name: "enable_unsupported_operation_status_code"
+    namespace: "machine_learning"
+    description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API."
+    bug: "301427767"
+}
+
+flag {
+    name: "enable_data_request_observer_api"
+    namespace: "machine_learning"
+    description: "This flag enables the API to register a data request observer on WearableSensingManager."
+    bug: "301427767"
+}
+
+flag {
+    name: "enable_provide_wearable_connection_api"
+    namespace: "machine_learning"
+    description: "This flag enables the WearableSensingManager#provideWearableConnection API."
+    bug: "301427767"
+}
+
+flag {
+    name: "enable_hotword_wearable_sensing_api"
+    namespace: "machine_learning"
+    description: "This flag enables the APIs related to hotword in WearableSensingManager and WearableSensingService."
+    bug: "310055381"
+}
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6204edc..eb82e1f 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,18 +822,7 @@
      *
      * @param appWidgetIds  The AppWidget instances to notify of view data changes.
      * @param viewId        The collection view id.
-     * @deprecated The corresponding API
-     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
-     * deprecated. Moving forward please use
-     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
-     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
-     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
-     * {@link #updateAppWidget(int, RemoteViews)},
-     * {@link #updateAppWidget(ComponentName, RemoteViews)},
-     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
-     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
-    @Deprecated
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (mService == null) {
             return;
@@ -884,18 +873,7 @@
      *
      * @param appWidgetId  The AppWidget instance to notify of view data changes.
      * @param viewId       The collection view id.
-     * @deprecated The corresponding API
-     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
-     * deprecated. Moving forward please use
-     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
-     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
-     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
-     * {@link #updateAppWidget(int, RemoteViews)},
-     * {@link #updateAppWidget(ComponentName, RemoteViews)},
-     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
-     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
-    @Deprecated
     public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
         if (mService == null) {
             return;
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 084cba3..822f02f 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -19,6 +19,9 @@
   namespace: "app_widgets"
   description: "Move state file IO to non-critical path"
   bug: "312949280"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index c1e443d..39f6de7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -26,6 +26,7 @@
 import android.companion.virtual.camera.VirtualCamera;
 import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtualdevice.flags.Flags;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -351,7 +352,11 @@
             @Nullable Executor executor,
             @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) {
         if (mVirtualAudioDevice == null) {
-            mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
+            Context context = mContext;
+            if (Flags.deviceAwareRecordAudioPermission()) {
+                context = mContext.createDeviceContext(getDeviceId());
+            }
+            mVirtualAudioDevice = new VirtualAudioDevice(context, mVirtualDevice, display,
                     executor, callback, () -> mVirtualAudioDevice = null);
         }
         return mVirtualAudioDevice;
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index d26890f..ab8db6e 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -21,3 +21,11 @@
      description: "Enable discovery of the Virtual Camera HAL without a VINTF entry"
      bug: "305170199"
 }
+
+flag {
+     namespace: "virtual_devices"
+     name: "device_aware_record_audio_permission"
+     description: "Enable device-aware RECORD_AUDIO permission"
+     bug: "291737188"
+     is_fixed_read_only: true
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bde562d..af13011 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -409,9 +409,10 @@
                     "packageName = " + mAttributionSourceState.packageName + ", " +
                     "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
                     "token = " + mAttributionSourceState.token + ", " +
+                    "deviceId = " + mAttributionSourceState.deviceId + ", " +
                     "next = " + (mAttributionSourceState.next != null
-                                    && mAttributionSourceState.next.length > 0
-                            ? mAttributionSourceState.next[0] : null) +
+                    && mAttributionSourceState.next.length > 0
+                    ? new AttributionSource(mAttributionSourceState.next[0]).toString() : null) +
                     " }";
         }
         return super.toString();
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index e9b94c9..87fb843 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -185,7 +185,7 @@
      *
      * @param context A Context object which should be some mock instance (like the
      * instance of {@link android.test.mock.MockContext}).
-     * @param readPermission The read permision you want this instance should have in the
+     * @param readPermission The read permission you want this instance should have in the
      * test, which is available via {@link #getReadPermission()}.
      * @param writePermission The write permission you want this instance should have
      * in the test, which is available via {@link #getWritePermission()}.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9253998..a126363 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -387,7 +387,7 @@
      * {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
      * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in
      * the arguments {@link Bundle}, the Content framework will attempt to
-     * synthesize an QUERY_ARG_SQL* argument using the corresponding
+     * synthesize a QUERY_ARG_SQL* argument using the corresponding
      * QUERY_ARG_SORT* values.
      */
     public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-sort-columns";
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 1f25fd0..451c0e5 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@
             long timeout);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
-    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+    void requestArchive(String packageName, String callerPackageName, int flags, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index ea69a2b..9f31aec 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,6 +21,7 @@
 import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.PackageInstaller;
+import android.content.pm.verify.domain.DomainSet;
 import android.content.IntentSender;
 import android.os.ParcelFileDescriptor;
 
@@ -73,4 +74,7 @@
     ParcelFileDescriptor getAppMetadataFd();
     ParcelFileDescriptor openWriteAppMetadata();
     void removeAppMetadata();
+
+    void setPreVerifiedDomains(in DomainSet preVerifiedDomains);
+    DomainSet getPreVerifiedDomains();
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 380de96..08f1853 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -843,4 +843,7 @@
     Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName);
 
     boolean isAppArchivable(String packageName, in UserHandle user);
+
+    @EnforcePermission("GET_APP_METADATA")
+    int getAppMetadataSource(String packageName, int userId);
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 50be983..7c264f6 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1802,25 +1802,16 @@
     }
 
     /**
-     * Enable or disable different archive compatibility options of the launcher.
+     * Disable different archive compatibility options of the launcher for the caller of this
+     * method.
      *
-     * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
-     * that a certain app is archived. True by default.
-     * Launchers might want to disable this operation if they want to provide custom user experience
-     * to differentiate archived apps.
-     * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
-     * they click an archived app, which explains that the app will be downloaded and restored in
-     * the background. True by default.
-     * Launchers might want to disable this operation if they provide sufficient, alternative user
-     * guidance to highlight that an unarchival is starting and ongoing once an archived app is
-     * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+     * @see ArchiveCompatibilityParams for individual options.
      */
     @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
-    public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
-            boolean enableUnarchivalConfirmation) {
+    public void setArchiveCompatibility(@NonNull ArchiveCompatibilityParams params) {
         try {
-            mService.setArchiveCompatibilityOptions(enableIconOverlay,
-                    enableUnarchivalConfirmation);
+            mService.setArchiveCompatibilityOptions(params.isEnableIconOverlay(),
+                    params.isEnableUnarchivalConfirmation());
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -1982,6 +1973,50 @@
         }
     };
 
+    /**
+     * Used to enable Archiving compatibility options with {@link #setArchiveCompatibility}.
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+    public static class ArchiveCompatibilityParams {
+        private boolean mEnableIconOverlay = true;
+
+        private boolean mEnableUnarchivalConfirmation = true;
+
+        /** @hide */
+        public boolean isEnableIconOverlay() {
+            return mEnableIconOverlay;
+        }
+
+        /** @hide */
+        public boolean isEnableUnarchivalConfirmation() {
+            return mEnableUnarchivalConfirmation;
+        }
+
+        /**
+         * If true, provides a cloud overlay for archived apps to ensure users are aware that a
+         * certain app is archived. True by default.
+         *
+         * <p> Launchers might want to disable this operation if they want to provide custom user
+         * experience to differentiate archived apps.
+         */
+        public void setEnableIconOverlay(boolean enableIconOverlay) {
+            this.mEnableIconOverlay = enableIconOverlay;
+        }
+
+        /**
+         * If true, the user is shown a confirmation dialog when they click an archived app, which
+         * explains that the app will be downloaded and restored in the background. True by default.
+         *
+         * <p> Launchers might want to disable this operation if they provide sufficient,
+         * alternative user guidance to highlight that an unarchival is starting and ongoing once an
+         * archived app is tapped. E.g., this could be achieved by showing the unarchival progress
+         * around the icon.
+         */
+        public void setEnableUnarchivalConfirmation(boolean enableUnarchivalConfirmation) {
+            this.mEnableUnarchivalConfirmation = enableUnarchivalConfirmation;
+        }
+    }
+
     private static class CallbackMessageHandler extends Handler {
         private static final int MSG_ADDED = 1;
         private static final int MSG_REMOVED = 2;
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 53dd3bf..fb95608895 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -10,3 +10,4 @@
 per-file UserInfo* = file:/MULTIUSER_OWNERS
 per-file *UserProperties* = file:/MULTIUSER_OWNERS
 per-file *multiuser* = file:/MULTIUSER_OWNERS
+per-file IBackgroundInstallControlService.aidl = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c4bf18d..c2ff9f6 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -61,6 +61,7 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
 import android.graphics.Bitmap;
 import android.icu.util.ULocale;
 import android.net.Uri;
@@ -2296,6 +2297,66 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * Sets the pre-verified domains for the app to be installed. By setting pre-verified
+         * domains, the installer allows the app to be opened by the app links of these domains
+         * immediately after it is installed.
+         *
+         * <p>The specified pre-verified domains should be a subset of the hostnames declared with
+         * {@code android:host} and {@code android:autoVerify=true} in the intent filters of the
+         * AndroidManifest.xml of the app. If some of the specified domains are not declared in
+         * the manifest, they will be ignored.</p>
+         * <p>If this API is called multiple times on the same {@link #Session}, the last call
+         * overrides the previous ones.</p>
+         * <p>The instant app installer is the only entity that may call this API.
+         * </p>
+         *
+         * @param preVerifiedDomains domains that are already pre-verified by the installer.
+         *
+         * @throws IllegalArgumentException if the number or the total size of the pre-verified
+         *                                  domains exceeds the maximum allowed, or if the domain
+         *                                  names contain invalid characters.
+         * @throws SecurityException if called from an installer that is not the instant app
+         *                           installer of the device, or if called after the session has
+         *                           been committed or abandoned.
+         *
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+        @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+        public void setPreVerifiedDomains(@NonNull Set<String> preVerifiedDomains) {
+            Preconditions.checkArgument(preVerifiedDomains != null && !preVerifiedDomains.isEmpty(),
+                    "Provided pre-verified domains cannot be null or empty.");
+            try {
+                mSession.setPreVerifiedDomains(new DomainSet(preVerifiedDomains));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Retrieve the pre-verified domains set in a session.
+         * See {@link #setPreVerifiedDomains(Set)} for the definition of pre-verified domains.
+         *
+         * @throws SecurityException if called from an installer that is not the owner of the
+         *                           session, or if called after the session has been committed or
+         *                           abandoned.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+        @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+        @NonNull
+        public Set<String> getPreVerifiedDomains() {
+            try {
+                DomainSet domainSet = mSession.getPreVerifiedDomains();
+                return domainSet != null ? domainSet.getDomains() : Collections.emptySet();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -2362,8 +2423,8 @@
     public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
             throws PackageManager.NameNotFoundException {
         try {
-            mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
-                    new UserHandle(mUserId));
+            mInstaller.requestArchive(packageName, mInstallerPackageName, /*flags=*/ 0,
+                    statusReceiver, new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8744eae..407ffbb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2992,6 +2992,46 @@
     @SystemApi
     public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
 
+
+    /**
+     * Indicates that the app metadata does not exist or its source is unknown.
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
+    @SystemApi
+    public static final int APP_METADATA_SOURCE_UNKNOWN = 0;
+    /**
+     * Indicates that the app metadata is provided by the APK itself.
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
+    @SystemApi
+    public static final int APP_METADATA_SOURCE_APK = 1;
+    /**
+     * Indicates that the app metadata is provided by the installer that installed the app.
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
+    @SystemApi
+    public static final int APP_METADATA_SOURCE_INSTALLER = 2;
+    /**
+     * Indicates that the app metadata is provided as part of the system image.
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
+    @SystemApi
+    public static final int APP_METADATA_SOURCE_SYSTEM_IMAGE = 3;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "APP_METADATA_SOURCE_" }, value = {
+            APP_METADATA_SOURCE_UNKNOWN,
+            APP_METADATA_SOURCE_APK,
+            APP_METADATA_SOURCE_INSTALLER,
+            APP_METADATA_SOURCE_SYSTEM_IMAGE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AppMetadataSource {}
+
     /**
      * Can be used as the {@code millisecondsToDelay} argument for
      * {@link PackageManager#extendVerificationTimeout}. This is the
@@ -4479,6 +4519,10 @@
      * the Android Keystore backed by an isolated execution environment. The version indicates
      * which features are implemented in the isolated execution environment:
      * <ul>
+     * <li>300: Ability to include a second IMEI in the ID attestation record, see
+     * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+     * <li>200: Hardware support for Curve 25519 (including both Ed25519 signature generation and
+     * X25519 key agreement).
      * <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
      * for app-generated attestation keys (see {@link
      * android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
@@ -4508,6 +4552,11 @@
      * StrongBox</a>. If this feature has a version, the version number indicates which features are
      * implemented in StrongBox:
      * <ul>
+     * <li>300: Ability to include a second IMEI in the ID attestation record, see
+     * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+     * <li>200: No new features for StrongBox (the Android Keystore environment backed by an
+     * isolated execution environment has gained support for Curve 25519 in this version, but
+     * the implementation backed by a dedicated secure processor is not expected to implement it).
      * <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
      * for app-generated attestation keys (see {@link
      * android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
@@ -6300,6 +6349,29 @@
         throw new UnsupportedOperationException("getAppMetadata not implemented in subclass");
     }
 
+
+    /**
+     * Returns the source of the app metadata that is currently associated with the given package.
+     * The value can be {@link #APP_METADATA_SOURCE_UNKNOWN}, {@link #APP_METADATA_SOURCE_APK},
+     * {@link #APP_METADATA_SOURCE_INSTALLER} or {@link #APP_METADATA_SOURCE_SYSTEM_IMAGE}.
+     *
+     * Note: an app can have the app metadata included in the APK, but if the installer also
+     * provides an app metadata during the installation, the one provided by the installer will
+     * take precedence.
+     *
+     * @param packageName The package name for which to get the app metadata source.
+     * @throws NameNotFoundException if no such package is available to the caller.
+     * @throws SecurityException if the caller doesn't have the required permission.
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.GET_APP_METADATA)
+    public @AppMetadataSource int getAppMetadataSource(@NonNull String packageName)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException("getAppMetadataSource not implemented in subclass");
+    }
+
     /**
      * Return a List of all installed packages that are currently holding any of
      * the given permissions.
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index b919c4b..db3050f 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -67,6 +67,18 @@
     /** @hide */
     public long dmSize;
 
+    /** Size of dexopt artifacts of the application. */
+    /** @hide */
+    public long dexoptSize;
+
+    /** Size of the current profile of the application. */
+    /** @hide */
+    public long curProfSize;
+
+    /** Size of the reference profile of the application. */
+    /** @hide */
+    public long refProfSize;
+
     /**
      * Size of the secure container on external storage holding the
      * application's code.
@@ -132,6 +144,18 @@
             sb.append(" dm=");
             sb.append(dmSize);
         }
+        if (dexoptSize != 0) {
+            sb.append(" dexopt=");
+            sb.append(dexoptSize);
+        }
+        if (curProfSize != 0) {
+            sb.append(" curProf=");
+            sb.append(curProfSize);
+        }
+        if (refProfSize != 0) {
+            sb.append(" refProf=");
+            sb.append(refProfSize);
+        }
         if (externalCodeSize != 0) {
             sb.append(" extCode=");
             sb.append(externalCodeSize);
@@ -176,6 +200,9 @@
         apkSize = source.readLong();
         libSize = source.readLong();
         dmSize = source.readLong();
+        dexoptSize = source.readLong();
+        curProfSize = source.readLong();
+        refProfSize = source.readLong();
         externalCodeSize = source.readLong();
         externalDataSize = source.readLong();
         externalCacheSize = source.readLong();
@@ -192,6 +219,9 @@
         apkSize = pStats.apkSize;
         libSize = pStats.libSize;
         dmSize = pStats.dmSize;
+        dexoptSize = pStats.dexoptSize;
+        curProfSize = pStats.curProfSize;
+        refProfSize = pStats.refProfSize;
         externalCodeSize = pStats.externalCodeSize;
         externalDataSize = pStats.externalDataSize;
         externalCacheSize = pStats.externalCacheSize;
@@ -212,6 +242,9 @@
         dest.writeLong(apkSize);
         dest.writeLong(libSize);
         dest.writeLong(dmSize);
+        dest.writeLong(dexoptSize);
+        dest.writeLong(curProfSize);
+        dest.writeLong(refProfSize);
         dest.writeLong(externalCodeSize);
         dest.writeLong(externalDataSize);
         dest.writeLong(externalCacheSize);
@@ -234,6 +267,9 @@
                 && apkSize == otherStats.apkSize
                 && libSize == otherStats.libSize
                 && dmSize == otherStats.dmSize
+                && dexoptSize == otherStats.dexoptSize
+                && curProfSize == otherStats.curProfSize
+                && refProfSize == otherStats.refProfSize
                 && externalCodeSize == otherStats.externalCodeSize
                 && externalDataSize == otherStats.externalDataSize
                 && externalCacheSize == otherStats.externalCacheSize
@@ -244,7 +280,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(packageName, userHandle, codeSize, dataSize,
-                apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+                apkSize, libSize, dmSize, dexoptSize, curProfSize,
+                refProfSize, cacheSize, externalCodeSize,
                 externalDataSize, externalCacheSize, externalMediaSize,
                 externalObbSize);
     }
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index 632c0f5..f84b46d 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -18,10 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
 import android.util.ArraySet;
 
 import com.android.internal.util.DataClass;
@@ -64,6 +62,12 @@
      */
     public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized;
 
+    /**
+     * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+     * If false (default), the parent app's configuration determines behavior.
+     */
+    public boolean useEmbeddedDex;
+
     @Deprecated
     public ProcessInfo(@NonNull ProcessInfo orig) {
         this.name = orig.name;
@@ -71,11 +75,12 @@
         this.gwpAsanMode = orig.gwpAsanMode;
         this.memtagMode = orig.memtagMode;
         this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
+        this.useEmbeddedDex = orig.useEmbeddedDex;
     }
 
 
 
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -102,6 +107,9 @@
      *   disabled, or left unspecified.
      * @param nativeHeapZeroInitialized
      *   Enable automatic zero-initialization of native heap memory allocations.
+     * @param useEmbeddedDex
+     *   Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+     *   If false (default), the parent app's configuration determines behavior.
      */
     @DataClass.Generated.Member
     public ProcessInfo(
@@ -109,7 +117,8 @@
             @Nullable ArraySet<String> deniedPermissions,
             @ApplicationInfo.GwpAsanMode int gwpAsanMode,
             @ApplicationInfo.MemtagMode int memtagMode,
-            @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+            @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+            boolean useEmbeddedDex) {
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
@@ -123,6 +132,7 @@
         this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
         com.android.internal.util.AnnotationValidations.validate(
                 ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+        this.useEmbeddedDex = useEmbeddedDex;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -145,6 +155,7 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         byte flg = 0;
+        if (useEmbeddedDex) flg |= 0x20;
         if (deniedPermissions != null) flg |= 0x2;
         dest.writeByte(flg);
         dest.writeString(name);
@@ -166,6 +177,7 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         byte flg = in.readByte();
+        boolean _useEmbeddedDex = (flg & 0x20) != 0;
         String _name = in.readString();
         ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
         int _gwpAsanMode = in.readInt();
@@ -185,6 +197,7 @@
         this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
         com.android.internal.util.AnnotationValidations.validate(
                 ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+        this.useEmbeddedDex = _useEmbeddedDex;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -204,10 +217,10 @@
     };
 
     @DataClass.Generated(
-            time = 1615850184524L,
-            codegenVersion = "1.0.22",
+            time = 1706177470784L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
-            inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  boolean useEmbeddedDex\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/SignedPackage.java b/core/java/android/content/pm/SignedPackage.java
new file mode 100644
index 0000000..4d1b136
--- /dev/null
+++ b/core/java/android/content/pm/SignedPackage.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A data class representing a package and (SHA-256 hash of) a signing certificate.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+public class SignedPackage {
+    @NonNull
+    private final SignedPackageParcel mData;
+
+    /** @hide */
+    public SignedPackage(@NonNull String pkgName, @NonNull byte[] certificateDigest) {
+        SignedPackageParcel data = new SignedPackageParcel();
+        data.pkgName = pkgName;
+        data.certificateDigest = certificateDigest;
+        mData = data;
+    }
+
+    /** @hide */
+    public SignedPackage(@NonNull SignedPackageParcel data) {
+        mData = data;
+    }
+
+    /** @hide */
+    public final @NonNull SignedPackageParcel getData() {
+        return mData;
+    }
+
+    public @NonNull String getPkgName() {
+        return mData.pkgName;
+    }
+
+    public @NonNull byte[] getCertificateDigest() {
+        return mData.certificateDigest;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SignedPackage that)) return false;
+        return mData.pkgName.equals(that.mData.pkgName) && Arrays.equals(mData.certificateDigest,
+                that.mData.certificateDigest);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mData.pkgName, Arrays.hashCode(mData.certificateDigest));
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/core/java/android/content/pm/SignedPackageParcel.aidl
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to core/java/android/content/pm/SignedPackageParcel.aidl
index a368de2..7957f7f 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/core/java/android/content/pm/SignedPackageParcel.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package android.content.pm;
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import android.content.ComponentName;
+
+/** @hide */
+parcelable SignedPackageParcel {
+    String pkgName;
+    byte[] certificateDigest;
+}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 1d0e2db..f54b2ac 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -77,6 +77,8 @@
     private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
             "crossProfileContentSharingStrategy";
     private static final String ATTR_PROFILE_API_VISIBILITY = "profileApiVisibility";
+    private static final String ITEMS_RESTRICTED_ON_HOME_SCREEN =
+            "itemsRestrictedOnHomeScreen";
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
             INDEX_SHOW_IN_LAUNCHER,
@@ -96,7 +98,8 @@
             INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
             INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
             INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
-            INDEX_PROFILE_API_VISIBILITY
+            INDEX_PROFILE_API_VISIBILITY,
+            INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -119,6 +122,7 @@
     private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
     private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
     private static final int INDEX_PROFILE_API_VISIBILITY = 17;
+    private static final int INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN = 18;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -532,6 +536,7 @@
             setDeleteAppWithParent(orig.getDeleteAppWithParent());
             setAlwaysVisible(orig.getAlwaysVisible());
             setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking());
+            setItemsRestrictedOnHomeScreen(orig.areItemsRestrictedOnHomeScreen());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
@@ -1014,6 +1019,38 @@
     }
     private @ProfileApiVisibility int mProfileApiVisibility;
 
+    /**
+     * Returns whether a user (usually a profile) is allowed to have items such as Apps Pending
+     * Installation, Widgets, Custom App Shortcuts, etc. on Launcher home screen.
+     *
+     * <p> For a typical user/profile, this property will be false, allowing framework APIs to
+     * provide information about such items to Launcher(s). When set true, framework APIs will
+     * restrict the same.
+     *
+     * <p> This property only restricts information about items that are accessed solely via the
+     * Launcher home screen. Information about items such as App Icons, Deep Links, which can also
+     * be accessed via other launcher components, such as All Apps Drawer is not restricted by this
+     * property.
+     *
+     * @hide
+     */
+    public boolean areItemsRestrictedOnHomeScreen() {
+        if (isPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN)) {
+            return mItemsRestrictedOnHomeScreen;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mItemsRestrictedOnHomeScreen;
+        }
+        throw new SecurityException(
+                "You don't have permission to query mItemsRestrictedOnHomeScreen");
+    }
+    /** @hide */
+    public void setItemsRestrictedOnHomeScreen(boolean val) {
+        this.mItemsRestrictedOnHomeScreen = val;
+        setPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN);
+    }
+    private boolean mItemsRestrictedOnHomeScreen;
+
     @Override
     public String toString() {
         String profileApiVisibility =
@@ -1042,7 +1079,8 @@
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
                 + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
-                + profileApiVisibility
+                + ", mProfileApiVisibility=" + profileApiVisibility
+                + ", mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen()
                 + "}";
     }
 
@@ -1079,6 +1117,7 @@
         if (android.multiuser.Flags.supportHidingProfiles()) {
             pw.println(prefix + "    mProfileApiVisibility=" + getProfileApiVisibility());
         }
+        pw.println(prefix + "    mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen());
     }
 
     /**
@@ -1168,6 +1207,9 @@
                         setProfileApiVisibility(parser.getAttributeInt(i));
                     }
                     break;
+                case ITEMS_RESTRICTED_ON_HOME_SCREEN:
+                    setItemsRestrictedOnHomeScreen(parser.getAttributeBoolean(i));
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -1256,6 +1298,10 @@
                         mProfileApiVisibility);
             }
         }
+        if (isPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN)) {
+            serializer.attributeBoolean(null, ITEMS_RESTRICTED_ON_HOME_SCREEN,
+                    mItemsRestrictedOnHomeScreen);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -1280,6 +1326,7 @@
         dest.writeBoolean(mAlwaysVisible);
         dest.writeInt(mCrossProfileContentSharingStrategy);
         dest.writeInt(mProfileApiVisibility);
+        dest.writeBoolean(mItemsRestrictedOnHomeScreen);
     }
 
     /**
@@ -1308,6 +1355,7 @@
         mAlwaysVisible = source.readBoolean();
         mCrossProfileContentSharingStrategy = source.readInt();
         mProfileApiVisibility = source.readInt();
+        mItemsRestrictedOnHomeScreen = source.readBoolean();
     }
 
     @Override
@@ -1358,6 +1406,7 @@
         private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
                 CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION;
         private @ProfileApiVisibility int mProfileApiVisibility = 0;
+        private boolean mItemsRestrictedOnHomeScreen = false;
 
         /**
          * @hide
@@ -1523,6 +1572,15 @@
             return this;
         }
 
+        /** Sets the value for {@link #mItemsRestrictedOnHomeScreen}
+         * @hide
+         */
+        public Builder setItemsRestrictedOnHomeScreen(
+                boolean itemsRestrictedOnHomeScreen) {
+            mItemsRestrictedOnHomeScreen = itemsRestrictedOnHomeScreen;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated.
          * @hide
          */
@@ -1548,7 +1606,8 @@
                     mDeleteAppWithParent,
                     mAlwaysVisible,
                     mCrossProfileContentSharingStrategy,
-                    mProfileApiVisibility);
+                    mProfileApiVisibility,
+                    mItemsRestrictedOnHomeScreen);
         }
     } // end Builder
 
@@ -1570,7 +1629,8 @@
             boolean deleteAppWithParent,
             boolean alwaysVisible,
             @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy,
-            @ProfileApiVisibility int profileApiVisibility) {
+            @ProfileApiVisibility int profileApiVisibility,
+            boolean itemsRestrictedOnHomeScreen) {
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
@@ -1593,5 +1653,6 @@
         if (android.multiuser.Flags.supportHidingProfiles()) {
             setProfileApiVisibility(profileApiVisibility);
         }
+        setItemsRestrictedOnHomeScreen(itemsRestrictedOnHomeScreen);
     }
 }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index caff457..f31521d 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -183,3 +183,26 @@
     description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
     bug: "321080601"
 }
+
+flag {
+    name: "asl_in_apk_app_metadata_source"
+    namespace: "package_manager_service"
+    description: "Feature flag to allow to know if the Android Safety Label (ASL) of an app is provided by the app's APK itself, or provided by an installer."
+    bug: "287487923"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "force_multi_arch_native_libs_match"
+    namespace: "package_manager_service"
+    description: "Feature flag to force an multiArch app's native libraries to match with the natively supported ABIs of the device"
+    bug: "282783453"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "set_pre_verified_domains"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable pre-verified domains"
+    bug: "307327678"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607..7ded747 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -44,6 +44,20 @@
 }
 
 flag {
+    name: "start_user_before_scheduled_alarms"
+    namespace: "multiuser"
+    description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
+    bug: "314907186"
+}
+
+flag {
+    name: "add_ui_for_sounds_from_background_users"
+    namespace: "multiuser"
+    description: "Allow foreground user to dismiss sounds that are coming from background users"
+    bug: "314907186"
+}
+
+flag {
     name: "enable_biometrics_to_unlock_private_space"
     namespace: "profile_experiences"
     description: "Add support to unlock the private space using biometrics"
@@ -108,3 +122,24 @@
     bug: "316362775"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_permission_to_access_hidden_profiles"
+    namespace: "profile_experiences"
+    description: "Add permission to access API hidden users data via system APIs"
+    bug: "321988638"
+}
+
+flag {
+    name: "handle_interleaved_settings_for_private_space"
+    namespace: "profile_experiences"
+    description: "Handle listing of private space apps in settings pages with interleaved content"
+    bug: "323212460"
+}
+
+flag {
+    name: "enable_hiding_profiles"
+    namespace: "profile_experiences"
+    description: "Allow the use of a profileApiAvailability user property to exclude HIDDEN profiles in API results"
+    bug: "316362775"
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index bd74b0b..a4db733 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,13 +49,6 @@
     public static class Builder {
         final OverlayPaths mPaths = new OverlayPaths();
 
-        public Builder() {}
-
-        public Builder(@NonNull OverlayPaths base) {
-            mPaths.mResourceDirs.addAll(base.getResourceDirs());
-            mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
-        }
-
         /**
          * Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
          */
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 4990a27..74ce62c 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -145,6 +145,11 @@
     private final boolean mUpdatableSystem;
 
     /**
+     * Name of the emergency installer for the designated system app.
+     */
+    private final @Nullable String mEmergencyInstaller;
+
+    /**
      * Archival install info.
      */
     private final @Nullable ArchivedPackageParcel mArchivedPackage;
@@ -159,7 +164,8 @@
             String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes,
-            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem) {
+            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
+            String emergencyInstaller) {
         mPath = path;
         mPackageName = packageName;
         mSplitName = splitName;
@@ -194,6 +200,7 @@
         mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
         mIsSdkLibrary = isSdkLibrary;
         mUpdatableSystem = updatableSystem;
+        mEmergencyInstaller = emergencyInstaller;
         mArchivedPackage = null;
     }
 
@@ -232,6 +239,7 @@
         mHasDeviceAdminReceiver = false;
         mIsSdkLibrary = false;
         mUpdatableSystem = true;
+        mEmergencyInstaller = null;
         mArchivedPackage = archivedPackage;
     }
 
@@ -550,6 +558,14 @@
     }
 
     /**
+     * Name of the emergency installer for the designated system app.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getEmergencyInstaller() {
+        return mEmergencyInstaller;
+    }
+
+    /**
      * Archival install info.
      */
     @DataClass.Generated.Member
@@ -558,10 +574,10 @@
     }
 
     @DataClass.Generated(
-            time = 1699587291575L,
+            time = 1706896661616L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 4626679..ffb69c0 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -39,6 +39,7 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.pm.pkg.component.flags.Flags;
 import com.android.internal.util.ArrayUtils;
 
 import libcore.io.IoUtils;
@@ -89,6 +90,8 @@
     private static final String TAG_SDK_LIBRARY = "sdk-library";
     private static final int SDK_VERSION = Build.VERSION.SDK_INT;
     private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+    private static final String TAG_PROCESSES = "processes";
+    private static final String TAG_PROCESS = "process";
 
     /**
      * Parse only lightweight details about the package at the given location.
@@ -432,6 +435,7 @@
         boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
                 "isSplitRequired", false);
         String configForSplit = parser.getAttributeValue(null, "configForSplit");
+        String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller");
 
         int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
         int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
@@ -518,6 +522,28 @@
                         case TAG_SDK_LIBRARY:
                             isSdkLibrary = true;
                             break;
+                        case TAG_PROCESSES:
+                            final int processesDepth = parser.getDepth();
+                            int processesType;
+                            while ((processesType = parser.next()) != XmlPullParser.END_DOCUMENT
+                                    && (processesType != XmlPullParser.END_TAG
+                                    || parser.getDepth() > processesDepth)) {
+                                if (processesType == XmlPullParser.END_TAG
+                                        || processesType == XmlPullParser.TEXT) {
+                                    continue;
+                                }
+
+                                if (parser.getDepth() != processesDepth + 1) {
+                                    // Search only under <processes>.
+                                    continue;
+                                }
+
+                                if (parser.getName().equals(TAG_PROCESS)
+                                        && Flags.enablePerProcessUseEmbeddedDexAttr()) {
+                                    useEmbeddedDex |= parser.getAttributeBooleanValue(
+                                            ANDROID_RES_NAMESPACE, "useEmbeddedDex", false);
+                                }
+                            }
                     }
                 }
             } else if (TAG_OVERLAY.equals(parser.getName())) {
@@ -619,7 +645,7 @@
                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
-                        hasDeviceAdminReceiver, isSdkLibrary, updatableSystem));
+                        hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller));
     }
 
     private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1b37092..3671980 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -29,6 +29,7 @@
 import android.annotation.DimenRes;
 import android.annotation.Discouraged;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.FontRes;
 import android.annotation.FractionRes;
 import android.annotation.IntegerRes;
@@ -46,6 +47,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
+import android.content.pm.ApplicationInfo;
 import android.content.res.loader.ResourcesLoader;
 import android.graphics.Movie;
 import android.graphics.Typeface;
@@ -2825,4 +2827,22 @@
             }
         }
     }
+
+    /**
+     * Register the resources paths of a package (e.g. a shared library). This will collect the
+     * package resources' paths from its ApplicationInfo and add them to all existing and future
+     * contexts while the application is running.
+     * A second call with the same uniqueId is a no-op.
+     * The paths are not persisted during application restarts. The application is responsible for
+     * calling the API again if this happens.
+     *
+     * @param uniqueId The unique id for the ApplicationInfo object, to detect and ignore repeated
+     *                 API calls.
+     * @param appInfo The ApplicationInfo that contains resources paths of the package.
+     */
+    @FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
+    public static void registerResourcePaths(@NonNull String uniqueId,
+            @NonNull ApplicationInfo appInfo) {
+        throw new UnsupportedOperationException("The implementation has not been done yet.");
+    }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c7790bd..5e442b8 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -273,14 +273,27 @@
         throw new NotFoundException("String resource name " + name);
     }
 
+    private static boolean isIntLike(@NonNull String s) {
+        if (s.isEmpty() || s.length() > 10) return false;
+        for (int i = 0, size = s.length(); i < size; i++) {
+            final char c = s.charAt(i);
+            if (c < '0' || c > '9') {
+                return false;
+            }
+        }
+        return true;
+    }
+
     int getIdentifier(String name, String defType, String defPackage) {
         if (name == null) {
             throw new NullPointerException("name is null");
         }
-        try {
-            return Integer.parseInt(name);
-        } catch (Exception e) {
-            // Ignore
+        if (isIntLike(name)) {
+            try {
+                return Integer.parseInt(name);
+            } catch (Exception e) {
+                // Ignore
+            }
         }
         return mAssets.getResourceIdentifier(name, defType, defPackage);
     }
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index db81e84..f660770 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -40,3 +40,12 @@
     description: "Feature flag for creating an frro from a 9-patch"
     bug: "309232726"
 }
+
+flag {
+    name: "register_resource_paths"
+    namespace: "resource_manager"
+    description: "Feature flag for register resource paths for shared library"
+    bug: "306202569"
+    # This flag is read in ResourcesImpl at boot time.
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 90cd471..ec46d2f 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -47,4 +47,18 @@
     name: "configurable_selector_ui_enabled"
     description: "Enables OEM configurable Credential Selector UI"
     bug: "319448437"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "credman_biometric_api_enabled"
+    description: "Enables Credential Manager to work with the Biometric Authenticate API"
+    bug: "323211850"
+}
+
+flag {
+    namespace: "wear_frameworks"
+    name: "wear_credential_manager_enabled"
+    description: "Enables Credential Manager on Wear Platform"
+    bug: "301168341"
+}
diff --git a/core/java/android/credentials/selection/AuthenticationEntry.java b/core/java/android/credentials/selection/AuthenticationEntry.java
index 54589e1..dd6ca9e 100644
--- a/core/java/android/credentials/selection/AuthenticationEntry.java
+++ b/core/java/android/credentials/selection/AuthenticationEntry.java
@@ -23,9 +23,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
 import android.app.slice.Slice;
+import android.content.Context;
 import android.content.Intent;
+import android.credentials.GetCredentialRequest;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,17 +37,20 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
 
 /**
  * An authentication entry.
  *
- * Applicable only for credential retrieval flow, authentication entries are a special type of
- * entries that require the user to unlock the given provider before its credential options can
- * be fully rendered.
+ * Applicable only for
+ * {@link android.credentials.CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} flow, authentication entries are a special type
+ * of entries that require the user to unlock the given provider before its credential options
+ * can be fully rendered.
  *
  * @hide
  */
-@TestApi
+@SystemApi
 @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class AuthenticationEntry implements Parcelable {
     @NonNull
@@ -67,17 +74,31 @@
     public @interface Status {
     }
 
-    /** This entry is still locked, as initially supplied by the provider. */
+    /**
+     * This entry is still locked, as initially supplied by the provider.
+     *
+     * This entry should be rendered in a way to signal that it is still locked, and when chosen
+     * will lead to an unlock challenge (e.g. draw a trailing lock icon on this entry).
+     */
     public static final int STATUS_LOCKED = 0;
     /**
      * This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
      * there is another such entry that was unlocked more recently.
+     *
+     * This entry should be rendered in a way to signal that it was unlocked but turns out to
+     * contain no credential that can be used, and as a result, it should be unclickable.
      */
     public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
     /**
      * This is the most recent entry that was unlocked but didn't contain any credential.
      *
      * There will be at most one authentication entry with this status.
+     *
+     * This entry should be rendered in a way to signal that it was unlocked but turns out to
+     * contain no credential that can be used, and as a result, it should be unclickable.
+     *
+     * If this was the last clickable option prior to unlocking, then the UI should display an
+     * information that all options are exhausted then gracefully finish itself.
      */
     public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
 
@@ -94,29 +115,36 @@
     }
 
     /**
-     * Constructor to be used for an entry that does not require further activities
-     * to be invoked when selected.
+     * Constructor to be used for an entry that requires a pending intent to be invoked
+     * when clicked.
+     *
+     * @param key    the identifier of this entry that's unique within the context of the given
+     *               CredentialManager request. This is used when constructing the
+     *               {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+     *                String providerId, String entryKey, String entrySubkey,
+     *                ProviderPendingIntentResponse providerPendingIntentResponse)}
+     *
+     * @param subkey the sub-identifier of this entry that's unique within the context of the
+     *               {@code key}. This is used when constructing the
+     *               {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+     *                String providerId, String entryKey, String entrySubkey,
+     *                ProviderPendingIntentResponse providerPendingIntentResponse)}
+     * @param intent the intent containing extra data that has to be filled in when launching this
+     *               entry's provider PendingIntent
+     * @param slice  the Slice to be displayed
+     * @param status the entry status, depending on which the entry should be rendered differently
      */
-    // TODO(b/322065508): remove this constructor.
     public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
-            @Status int status) {
+            @Status int status, @NonNull Intent intent) {
         mKey = key;
         mSubkey = subkey;
         mSlice = slice;
         mStatus = status;
-    }
-
-    /** Constructor to be used for an entry that requires a pending intent to be invoked
-     * when clicked.
-     */
-    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
-            @Status int status, @NonNull Intent intent) {
-        this(key, subkey, slice, status);
         mFrameworkExtrasIntent = intent;
     }
 
     /**
-     * Returns the identifier of this entry that's unique within the context of the
+     * Returns the identifier of this entry that's unique within the context of the given
      * CredentialManager request.
      */
     @NonNull
@@ -138,7 +166,7 @@
         return mSlice;
     }
 
-    /** Returns the entry status, depending on which the entry will be rendered differently. */
+    /** Returns the entry status, depending on which the entry should be rendered differently. */
     @NonNull
     @Status
     public int getStatus() {
@@ -146,8 +174,10 @@
     }
 
     /**
-     * Returns the framework intent to be filled in when launching this entry's provider
-     * PendingIntent.
+     * Returns the intent containing extra data that has to be filled in when launching this
+     * entry's provider PendingIntent.
+     *
+     * If null, the provider PendingIntent can be launched without any fill in intent.
      */
     @Nullable
     @SuppressLint("IntentBuilderName") // Not building a new intent.
diff --git a/core/java/android/credentials/selection/CancelSelectionRequest.java b/core/java/android/credentials/selection/CancelSelectionRequest.java
new file mode 100644
index 0000000..2662d76
--- /dev/null
+++ b/core/java/android/credentials/selection/CancelSelectionRequest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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 android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request to cancel the ongoing selection UI matching the identifier token in this request.
+ *
+ * Upon receiving this request, the UI should gracefully finish itself if the given request token
+ * {@link CancelSelectionRequest#getToken()} matches that of the selection UI is currently rendered
+ * for. Also, the UI should display some informational cancellation message (e.g. "Request is
+ * cancelled by the app") before closing when the
+ * {@link CancelSelectionRequest#shouldShowCancellationExplanation()} is true.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class CancelSelectionRequest implements Parcelable {
+
+    /**
+     * The intent extra key for the {@code CancelUiRequest} object when launching the UX
+     * activities.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String EXTRA_CANCEL_UI_REQUEST =
+            "android.credentials.selection.extra.CANCEL_UI_REQUEST";
+
+    @NonNull
+    private final IBinder mToken;
+
+    private final boolean mShouldShowCancellationExplanation;
+
+    @NonNull
+    private final String mAppPackageName;
+
+    /**
+     * Returns the request token matching the user request that should be cancelled.
+     *
+     * The request token for the current UI can be found from the UI launch intent, mapping to
+     * {@link RequestInfo#getToken()}.
+     *
+     * @hide
+     */
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    /** Returns the request token matching the app request that should be cancelled. */
+    @NonNull
+    public RequestToken getRequestToken() {
+        return new RequestToken(mToken);
+    }
+
+    /**
+     * Returns the app package name invoking this request, that can be used to derive display
+     * metadata (e.g. "Cancelled by `App Name`").
+     */
+    @NonNull
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
+
+    /**
+     * Returns whether the UI should display some informational cancellation message (e.g.
+     * "Request is cancelled by the app") before closing. If false, the UI should be silently
+     * cancelled.
+     */
+    public boolean shouldShowCancellationExplanation() {
+        return mShouldShowCancellationExplanation;
+    }
+
+    /**
+     * Constructs a {@link CancelSelectionRequest}.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+    public CancelSelectionRequest(@NonNull IBinder token, boolean shouldShowCancellationExplanation,
+            @NonNull String appPackageName) {
+        mToken = token;
+        mShouldShowCancellationExplanation = shouldShowCancellationExplanation;
+        mAppPackageName = appPackageName;
+    }
+
+    private CancelSelectionRequest(@NonNull Parcel in) {
+        mToken = in.readStrongBinder();
+        AnnotationValidations.validate(NonNull.class, null, mToken);
+        mShouldShowCancellationExplanation = in.readBoolean();
+        mAppPackageName = in.readString8();
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeBoolean(mShouldShowCancellationExplanation);
+        dest.writeString8(mAppPackageName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<CancelSelectionRequest> CREATOR = new Creator<>() {
+        @Override
+        public CancelSelectionRequest createFromParcel(@NonNull Parcel in) {
+            return new CancelSelectionRequest(in);
+        }
+
+        @Override
+        public CancelSelectionRequest[] newArray(int size) {
+            return new CancelSelectionRequest[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/selection/CancelUiRequest.java b/core/java/android/credentials/selection/CancelUiRequest.java
deleted file mode 100644
index fca0e2a..0000000
--- a/core/java/android/credentials/selection/CancelUiRequest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 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 android.credentials.selection;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * A request to cancel the ongoing UI matching the identifier token in this request.
- *
- * @hide
- */
-public final class CancelUiRequest implements Parcelable {
-
-    /**
-     * The intent extra key for the {@code CancelUiRequest} object when launching the UX
-     * activities.
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String EXTRA_CANCEL_UI_REQUEST =
-            "android.credentials.selection.extra.CANCEL_UI_REQUEST";
-
-    @NonNull
-    private final IBinder mToken;
-
-    private final boolean mShouldShowCancellationUi;
-
-    @NonNull
-    private final String mAppPackageName;
-
-    /** Returns the request token matching the user request that should be cancelled. */
-    @NonNull
-    public IBinder getToken() {
-        return mToken;
-    }
-
-    /**
-     * Returns the app package name invoking this request, that can be used to derive display
-     * metadata (e.g. "Cancelled by `App Name`").
-     */
-    @NonNull
-    public String getAppPackageName() {
-        return mAppPackageName;
-    }
-
-    /**
-     * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
-     * will be silently cancelled.
-     */
-    public boolean shouldShowCancellationUi() {
-        return mShouldShowCancellationUi;
-    }
-
-    /** Constructs a {@link CancelUiRequest}. */
-    public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
-            @NonNull String appPackageName) {
-        mToken = token;
-        mShouldShowCancellationUi = shouldShowCancellationUi;
-        mAppPackageName = appPackageName;
-    }
-
-    private CancelUiRequest(@NonNull Parcel in) {
-        mToken = in.readStrongBinder();
-        AnnotationValidations.validate(NonNull.class, null, mToken);
-        mShouldShowCancellationUi = in.readBoolean();
-        mAppPackageName = in.readString8();
-        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongBinder(mToken);
-        dest.writeBoolean(mShouldShowCancellationUi);
-        dest.writeString8(mAppPackageName);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @NonNull
-    public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
-        @Override
-        public CancelUiRequest createFromParcel(@NonNull Parcel in) {
-            return new CancelUiRequest(in);
-        }
-
-        @Override
-        public CancelUiRequest[] newArray(int size) {
-            return new CancelUiRequest[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/selection/CreateCredentialProviderData.java b/core/java/android/credentials/selection/CreateCredentialProviderData.java
index fc80ea8..ba9b00a 100644
--- a/core/java/android/credentials/selection/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/selection/CreateCredentialProviderData.java
@@ -118,9 +118,12 @@
     @TestApi
     @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
     public static final class Builder {
-        @NonNull private String mProviderFlattenedComponentName;
-        @NonNull private List<Entry> mSaveEntries = new ArrayList<>();
-        @Nullable private Entry mRemoteEntry = null;
+        @NonNull
+        private String mProviderFlattenedComponentName;
+        @NonNull
+        private List<Entry> mSaveEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
 
         /** Constructor with required properties. */
         public Builder(@NonNull String providerFlattenedComponentName) {
diff --git a/core/java/android/credentials/selection/CreateCredentialProviderInfo.java b/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
index 78b9fd44..6d02f0d 100644
--- a/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
+++ b/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
@@ -16,21 +16,40 @@
 
 package android.credentials.selection;
 
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
 
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
- * Information pertaining to a specific provider during the given create-credential flow.
+ * Per-provider metadata and entries for the
+ * {@link android.credentials.CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} flow.
  *
  * This includes provider metadata and its credential creation options for display purposes.
  *
+ * The selection UI should render all options (from
+ * {@link CreateCredentialProviderInfo#getSaveEntries()} and
+ * {@link CreateCredentialProviderInfo#getRemoteEntry()}) offered by this provider while clearly
+ * associating them with the given provider using the provider icon, label, etc. derived from
+ * {@link CreateCredentialProviderInfo#getProviderName()}.
+ *
  * @hide
  */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class CreateCredentialProviderInfo {
 
     @NonNull
@@ -65,7 +84,8 @@
      * Returns the remote credential saving option, if any.
      *
      * Notice that only one system configured provider can set this option, and when set, it means
-     * that the system service has already validated the provider's eligibility.
+     * that the system service has already validated the provider's eligibility. A null value means
+     * no remote entry should be displayed for this provider.
      */
     @Nullable
     public Entry getRemoteEntry() {
@@ -77,6 +97,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
     public static final class Builder {
         @NonNull
         private String mProviderName;
@@ -85,7 +107,12 @@
         @Nullable
         private Entry mRemoteEntry = null;
 
-        /** Constructor with required properties. */
+        /**
+         * Constructs a {@link CreateCredentialProviderInfo.Builder}.
+         *
+         * @param providerName the provider (component or package) name
+         * @throws IllegalArgumentException if {@code providerName} is null or empty
+         */
         public Builder(@NonNull String providerName) {
             mProviderName = Preconditions.checkStringNotEmpty(providerName);
         }
@@ -97,7 +124,13 @@
             return this;
         }
 
-        /** Sets the remote entry of the provider. */
+        /**
+         * Sets the remote entry to be displayed to the user.
+         *
+         * The system service should only set this entry to non-null if it has validated that
+         * the given provider does have the permission to set this value. Null means there is
+         * no valid remote entry for display.
+         */
         @NonNull
         public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
             mRemoteEntry = remoteEntry;
diff --git a/core/java/android/credentials/selection/DisabledProviderData.java b/core/java/android/credentials/selection/DisabledProviderData.java
index b6f6ad4..4238612 100644
--- a/core/java/android/credentials/selection/DisabledProviderData.java
+++ b/core/java/android/credentials/selection/DisabledProviderData.java
@@ -63,14 +63,14 @@
     }
 
     public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() {
-                @Override
-                public DisabledProviderData createFromParcel(@NonNull Parcel in) {
-                    return new DisabledProviderData(in);
-                }
+        @Override
+        public DisabledProviderData createFromParcel(@NonNull Parcel in) {
+            return new DisabledProviderData(in);
+        }
 
-                @Override
-                public DisabledProviderData[] newArray(int size) {
-                    return new DisabledProviderData[size];
-                }
+        @Override
+        public DisabledProviderData[] newArray(int size) {
+            return new DisabledProviderData[size];
+        }
     };
 }
diff --git a/core/java/android/credentials/selection/DisabledProviderInfo.java b/core/java/android/credentials/selection/DisabledProviderInfo.java
index 7d7dbc2..7764d2e 100644
--- a/core/java/android/credentials/selection/DisabledProviderInfo.java
+++ b/core/java/android/credentials/selection/DisabledProviderInfo.java
@@ -16,17 +16,36 @@
 
 package android.credentials.selection;
 
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.concurrent.Executor;
+
 /**
  * Information pertaining to a specific provider that is disabled from the user settings.
  *
- * Currently, disabled provider data is only propagated in the create-credential flow.
+ * Currently, disabled provider data is only propagated in the
+ * {@link android.credentials.CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} flow.
+ *
+ * This should be used to display an option, e.g. "+ Enable `disabled_provider_1`,
+ * `disabled_provider_2`" to navigate the user to Settings
+ * ({@link android.provider.Settings#ACTION_CREDENTIAL_PROVIDER}) to enable these
+ * disabled providers.
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class DisabledProviderInfo {
 
     @NonNull
@@ -37,8 +56,7 @@
      *
      * @throws IllegalArgumentException if {@code providerName} is empty
      */
-    public DisabledProviderInfo(
-            @NonNull String providerName) {
+    public DisabledProviderInfo(@NonNull String providerName) {
         mProviderName = Preconditions.checkStringNotEmpty(providerName);
     }
 
diff --git a/core/java/android/credentials/selection/Entry.java b/core/java/android/credentials/selection/Entry.java
index bcf4ee3..a131f3f 100644
--- a/core/java/android/credentials/selection/Entry.java
+++ b/core/java/android/credentials/selection/Entry.java
@@ -22,7 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.app.slice.Slice;
 import android.content.Intent;
@@ -36,7 +36,7 @@
  *
  * @hide
  */
-@TestApi
+@SystemApi
 @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class Entry implements Parcelable {
     @NonNull
@@ -67,30 +67,34 @@
     }
 
     /**
-     * Constructor to be used for an entry that does not require further activities
-     * to be invoked when selected.
-     */
-    // TODO(b/322065508): deprecate this constructor.
-    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
-        mKey = key;
-        mSubkey = subkey;
-        mSlice = slice;
-    }
-
-    /**
      * Constructor to be used for an entry that requires a pending intent to be invoked
      * when clicked.
+     *
+     * @param key    the identifier of this entry that's unique within the context of the given
+     *               CredentialManager request. This is used when constructing the
+     *               {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+     *                String providerId, String entryKey, String entrySubkey,
+     *                ProviderPendingIntentResponse providerPendingIntentResponse)}
+     * @param subkey the sub-identifier of this entry that's unique within the context of the
+     *               {@code key}. This is used when constructing the
+     *               {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
+     *                String providerId, String entryKey, String entrySubkey,
+     *                ProviderPendingIntentResponse providerPendingIntentResponse)}
+     * @param intent the intent containing extra data that has to be filled in when launching this
+     *               entry's provider PendingIntent
+     * @param slice  the Slice to be displayed
      */
     public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
             @NonNull Intent intent) {
-        this(key, subkey, slice);
+        mKey = key;
+        mSubkey = subkey;
+        mSlice = slice;
         mFrameworkExtrasIntent = intent;
     }
 
     /**
      * Returns the identifier of this entry that's unique within the context of the
-     * CredentialManager
-     * request.
+     * CredentialManager request.
      *
      * Generally used when sending the user selection result back to the system service.
      */
@@ -116,17 +120,10 @@
     }
 
     /**
-     * Returns the provider PendingIntent to launch once this entry is selected.
-     */
-    // TODO(b/322065508): deprecate this bit.
-    @Nullable
-    public PendingIntent getPendingIntent() {
-        return mPendingIntent;
-    }
-
-    /**
-     * Returns the framework fill in intent to add to the provider PendingIntent to launch, once
-     * this entry is selected.
+     * Returns the intent containing extra data that has to be filled in when launching this
+     * entry's provider PendingIntent.
+     *
+     * If null, the provider PendingIntent can be launched without any fill in intent.
      */
     @Nullable
     @SuppressLint("IntentBuilderName") // Not building a new intent.
diff --git a/core/java/android/credentials/selection/GetCredentialProviderInfo.java b/core/java/android/credentials/selection/GetCredentialProviderInfo.java
index db0fb84..1e2f27c 100644
--- a/core/java/android/credentials/selection/GetCredentialProviderInfo.java
+++ b/core/java/android/credentials/selection/GetCredentialProviderInfo.java
@@ -16,21 +16,45 @@
 
 package android.credentials.selection;
 
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.credentials.GetCredentialRequest;
+import android.credentials.PrepareGetCredentialResponse;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
 
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
- * Information pertaining to a specific provider during the given create-credential flow.
+ * Information pertaining to a specific provider during the given
+ * {@link android.credentials.CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} or
+ * {@link android.credentials.CredentialManager#getCredential(Context,
+ * PrepareGetCredentialResponse.PendingGetCredentialHandle, CancellationSignal, Executor,
+ * OutcomeReceiver)} flow.
  *
  * This includes provider metadata and its credential creation options for display purposes.
  *
+ * The selection UI should render all options (from
+ * {@link GetCredentialProviderInfo#getRemoteEntry()},
+ * {@link GetCredentialProviderInfo#getCredentialEntries()}, and
+ * {@link GetCredentialProviderInfo#getActionChips()}) offered by this provider while clearly
+ * associated them with the given provider using the provider icon, label, etc. derived from
+ * {@link GetCredentialProviderInfo#getProviderName()}.
+ *
  * @hide
  */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class GetCredentialProviderInfo {
 
     @NonNull
@@ -95,8 +119,9 @@
     /**
      * Returns the remote credential retrieval option, if any.
      *
-     * Notice that only one system configured provider can set this option, and when set, it means
-     * that the system service has already validated the provider's eligibility.
+     * Notice that only one system configured provider can set this option, and when set to
+     * non-null, it means that the system service has already validated the provider's eligibility.
+     * A null value means no remote entry should be displayed for this provider.
      */
     @Nullable
     public Entry getRemoteEntry() {
@@ -108,6 +133,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
     public static final class Builder {
         @NonNull
         private String mProviderName;
@@ -123,6 +150,7 @@
         /**
          * Constructs a {@link GetCredentialProviderInfo.Builder}.
          *
+         * @param providerName the provider (component or package) name
          * @throws IllegalArgumentException if {@code providerName} is null or empty
          */
         public Builder(@NonNull String providerName) {
@@ -151,7 +179,13 @@
             return this;
         }
 
-        /** Sets the remote entry to be displayed to the user. */
+        /**
+         * Sets the remote entry to be displayed to the user.
+         *
+         * The system service should only set this entry to non-null if it has validated that
+         * the given provider does have the permission to set this value. Null means there is
+         * no valid remote entry for display.
+         */
         @NonNull
         public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
             mRemoteEntry = remoteEntry;
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index c3a09ae..1837976 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -17,17 +17,24 @@
 package android.credentials.selection;
 
 import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+import static android.credentials.flags.Flags.configurableSelectorUiEnabled;
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.ComponentName;
+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.os.IBinder;
 import android.os.Parcel;
 import android.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Slog;
 
 import java.util.ArrayList;
 
@@ -41,15 +48,17 @@
 public class IntentFactory {
 
     /**
-     * Generate a new launch intent to the Credential Selector UI.
+     * Generate a new launch intent to the Credential Selector UI for auto-filling.
      *
      * @hide
      */
     @NonNull
+    // TODO(b/323552850) - clean up method overloads
     public static Intent createCredentialSelectorIntent(
+            @NonNull Context context,
             @NonNull RequestInfo requestInfo,
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-            @NonNull
+            @Nullable
             ArrayList<ProviderData> enabledProviderDataList,
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
             @NonNull
@@ -57,23 +66,31 @@
             @NonNull ResultReceiver resultReceiver,
             boolean isRequestForAllOptions) {
 
-        Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
-                disabledProviderDataList, resultReceiver);
+        Intent intent;
+        if (enabledProviderDataList != null) {
+            intent = createCredentialSelectorIntent(context, requestInfo, enabledProviderDataList,
+                    disabledProviderDataList, resultReceiver);
+        } else {
+            intent = createCredentialSelectorIntent(context, requestInfo,
+                    disabledProviderDataList, resultReceiver);
+        }
         intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
 
         return intent;
     }
 
-    /** Generate a new launch intent to the Credential Selector UI. */
+    /**
+     * Generate a new launch intent to the Credential Selector UI.
+     *
+     * @hide
+     */
     @NonNull
-    public static Intent createCredentialSelectorIntent(
+    private static Intent createCredentialSelectorIntent(
+            @NonNull Context context,
             @NonNull RequestInfo requestInfo,
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-                    @NonNull
-                    ArrayList<ProviderData> enabledProviderDataList,
-            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-                    @NonNull
-                    ArrayList<DisabledProviderData> disabledProviderDataList,
+            @NonNull
+            ArrayList<DisabledProviderData> disabledProviderDataList,
             @NonNull ResultReceiver resultReceiver) {
         Intent intent = new Intent();
         ComponentName componentName =
@@ -82,10 +99,11 @@
                                 .getString(
                                         com.android.internal.R.string
                                                 .config_credentialManagerDialogComponent));
+        ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
+        if (oemOverrideComponentName != null) {
+            componentName = oemOverrideComponentName;
+        }
         intent.setComponent(componentName);
-
-        intent.putParcelableArrayListExtra(
-                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
         intent.putParcelableArrayListExtra(
                 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
         intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -96,9 +114,89 @@
     }
 
     /**
-     * Creates an Intent that cancels any UI matching the given request token id.
+     * Returns null if there is not an enabled and valid oem override component. It means the
+     * default platform UI component name should be used instead.
+     */
+    @Nullable
+    private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
+        ComponentName result = null;
+        if (configurableSelectorUiEnabled()) {
+            if (Resources.getSystem().getBoolean(
+                    com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) {
+                String oemComponentString =
+                        Resources.getSystem()
+                                .getString(
+                                        com.android.internal.R.string
+                                                .config_oemCredentialManagerDialogComponent);
+                if (!TextUtils.isEmpty(oemComponentString)) {
+                    ComponentName oemComponentName = ComponentName.unflattenFromString(
+                            oemComponentString);
+                    if (oemComponentName != null) {
+                        try {
+                            ActivityInfo info = context.getPackageManager().getActivityInfo(
+                                    oemComponentName,
+                                    PackageManager.ComponentInfoFlags.of(
+                                            PackageManager.MATCH_SYSTEM_ONLY));
+                            if (info.enabled && info.exported) {
+                                Slog.i(TAG,
+                                        "Found enabled oem CredMan UI component."
+                                                + oemComponentString);
+                                result = oemComponentName;
+                            } else {
+                                Slog.i(TAG,
+                                        "Found enabled oem CredMan UI component but it was not "
+                                                + "enabled.");
+                            }
+                        } catch (PackageManager.NameNotFoundException e) {
+                            Slog.i(TAG, "Unable to find oem CredMan UI component: "
+                                    + oemComponentString + ".");
+                        }
+                    } else {
+                        Slog.i(TAG, "Invalid OEM ComponentName format.");
+                    }
+                } else {
+                    Slog.i(TAG, "Invalid empty OEM component name.");
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Generate a new launch intent to the Credential Selector UI.
      *
-     * @hide
+     * @param context                  the CredentialManager system service (only expected caller)
+     *                                 context that may be used to query existence of the key UI
+     *                                 application
+     * @param disabledProviderDataList the list of disabled provider data that when non-empty the
+     *                                 UI should accordingly generate an entry suggesting the user
+     *                                 to navigate to settings and enable them
+     * @param enabledProviderDataList  the list of enabled provider that contain options for this
+     *                                 request; the UI should render each option to the user for
+     *                                 selection
+     * @param requestInfo              the display information about the given app request
+     * @param resultReceiver           used by the UI to send the UI selection result back
+     */
+    @NonNull
+    public static Intent createCredentialSelectorIntent(
+            @NonNull Context context,
+            @NonNull RequestInfo requestInfo,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+            @NonNull
+            ArrayList<ProviderData> enabledProviderDataList,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+            @NonNull
+            ArrayList<DisabledProviderData> disabledProviderDataList,
+            @NonNull ResultReceiver resultReceiver) {
+        Intent intent = createCredentialSelectorIntent(context, requestInfo,
+                disabledProviderDataList, resultReceiver);
+        intent.putParcelableArrayListExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
+        return intent;
+    }
+
+    /**
+     * Creates an Intent that cancels any UI matching the given request token id.
      */
     @NonNull
     public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
@@ -111,8 +209,8 @@
                                         com.android.internal.R.string
                                                 .config_credentialManagerDialogComponent));
         intent.setComponent(componentName);
-        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
-                new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
+        intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+                new CancelSelectionRequest(requestToken, shouldShowCancellationUi, appPackageName));
         return intent;
     }
 
@@ -132,5 +230,8 @@
         return ipcFriendly;
     }
 
-    private IntentFactory() {}
+    private IntentFactory() {
+    }
+
+    private static final String TAG = "CredManIntentHelper";
 }
diff --git a/core/java/android/credentials/selection/IntentHelper.java b/core/java/android/credentials/selection/IntentHelper.java
index 6bcd05a..2c3a320 100644
--- a/core/java/android/credentials/selection/IntentHelper.java
+++ b/core/java/android/credentials/selection/IntentHelper.java
@@ -16,12 +16,16 @@
 
 package android.credentials.selection;
 
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.Intent;
 import android.os.ResultReceiver;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -29,15 +33,17 @@
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class IntentHelper {
     /**
-     * Attempts to extract a {@link CancelUiRequest} from the given intent; returns null
+     * Attempts to extract a {@link CancelSelectionRequest} from the given intent; returns null
      * if not found.
      */
     @Nullable
-    public static CancelUiRequest extractCancelUiRequest(@NonNull Intent intent) {
-        return intent.getParcelableExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
-                CancelUiRequest.class);
+    public static CancelSelectionRequest extractCancelUiRequest(@NonNull Intent intent) {
+        return intent.getParcelableExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+                CancelSelectionRequest.class);
     }
 
     /**
@@ -52,37 +58,44 @@
 
     /**
      * Attempts to extract the list of {@link GetCredentialProviderInfo} from the given intent;
-     * returns null if not found.
+     * returns an empty list if not found.
      */
-    @Nullable
-    @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
-    // and the other APIs in this class.
-    public static List<GetCredentialProviderInfo> extractGetCredentialProviderDataList(
+    public static @NonNull List<GetCredentialProviderInfo> extractGetCredentialProviderInfoList(
             @NonNull Intent intent) {
         List<GetCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
                 ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
                 GetCredentialProviderData.class);
-        return providerList == null ? null : providerList.stream().map(
+        return providerList == null ? Collections.emptyList() : providerList.stream().map(
                 GetCredentialProviderData::toGetCredentialProviderInfo).toList();
     }
 
     /**
      * Attempts to extract the list of {@link CreateCredentialProviderInfo} from the given intent;
-     * returns null if not found.
+     * returns an empty list if not found.
      */
-    @Nullable
-    @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
-    // and the other APIs in this class.
-    public static List<CreateCredentialProviderInfo> extractCreateCredentialProviderDataList(
-            @NonNull Intent intent) {
+    public static @NonNull List<CreateCredentialProviderInfo>
+            extractCreateCredentialProviderInfoList(@NonNull Intent intent) {
         List<CreateCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
                 ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
                 CreateCredentialProviderData.class);
-        return providerList == null ? null : providerList.stream().map(
+        return providerList == null ? Collections.emptyList() : providerList.stream().map(
                 CreateCredentialProviderData::toCreateCredentialProviderInfo).toList();
     }
 
     /**
+     * Attempts to extract the list of {@link DisabledProviderInfo} from the given intent;
+     * returns an empty list if not found.
+     */
+    public static @NonNull List<DisabledProviderInfo> extractDisabledProviderInfoList(
+            @NonNull Intent intent) {
+        List<DisabledProviderData> providerList = intent.getParcelableArrayListExtra(
+                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
+                DisabledProviderData.class);
+        return providerList == null ? Collections.emptyList() : providerList.stream().map(
+                DisabledProviderData::toDisabledProviderInfo).toList();
+    }
+
+    /**
      * Attempts to extract a {@link android.os.ResultReceiver} from the given intent, which should
      * be used to send back UI results; returns null if not found.
      */
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 7d6ea7e..60bbae6 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
@@ -41,13 +42,15 @@
  *
  * @hide
  */
-@TestApi
+@SystemApi
 @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
 public final class RequestInfo implements Parcelable {
 
     /**
      * The intent extra key for the {@code RequestInfo} object when launching the UX
      * activities.
+     *
+     * @hide
      */
     @NonNull
     public static final String EXTRA_REQUEST_INFO =
@@ -79,7 +82,7 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @StringDef(value = {TYPE_GET, TYPE_CREATE})
+    @StringDef(value = {TYPE_GET, TYPE_CREATE, TYPE_UNDEFINED})
     public @interface RequestType {
     }
 
@@ -107,48 +110,41 @@
 
     private final boolean mHasPermissionToOverrideDefault;
 
-    /** Creates new {@code RequestInfo} for a create-credential flow. */
-    @NonNull
-    public static RequestInfo newCreateRequestInfo(
-            @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            @NonNull String appPackageName) {
-        return new RequestInfo(
-                token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                /*hasPermissionToOverrideDefault=*/ false,
-                /*defaultProviderIds=*/ new ArrayList<>());
-    }
+    private final boolean mIsShowAllOptionsRequested;
 
-    /** Creates new {@code RequestInfo} for a create-credential flow. */
+    /**
+     * Creates new {@code RequestInfo} for a create-credential flow.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
     @NonNull
     public static RequestInfo newCreateRequestInfo(
             @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
             @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
-            @NonNull List<String> defaultProviderIds) {
+            @NonNull List<String> defaultProviderIds, boolean isShowAllOptionsRequested) {
         return new RequestInfo(
                 token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                hasPermissionToOverrideDefault, defaultProviderIds);
+                hasPermissionToOverrideDefault, defaultProviderIds, isShowAllOptionsRequested);
     }
 
-    /** Creates new {@code RequestInfo} for a get-credential flow. */
+    /**
+     * Creates new {@code RequestInfo} for a get-credential flow.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
     @NonNull
     public static RequestInfo newGetRequestInfo(
             @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+            boolean isShowAllOptionsRequested) {
         return new RequestInfo(
                 token, TYPE_GET, appPackageName, null, getCredentialRequest,
                 hasPermissionToOverrideDefault,
-                /*defaultProviderIds=*/ new ArrayList<>());
-    }
-
-    /** Creates new {@code RequestInfo} for a get-credential flow. */
-    @NonNull
-    public static RequestInfo newGetRequestInfo(
-            @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            @NonNull String appPackageName) {
-        return new RequestInfo(
-                token, TYPE_GET, appPackageName, null, getCredentialRequest,
-                /*hasPermissionToOverrideDefault=*/ false,
-                /*defaultProviderIds=*/ new ArrayList<>());
+                /*defaultProviderIds=*/ new ArrayList<>(), isShowAllOptionsRequested);
     }
 
 
@@ -157,7 +153,11 @@
         return mHasPermissionToOverrideDefault;
     }
 
-    /** Returns the request token matching the user request. */
+    /**
+     * Returns the request token matching the user request.
+     *
+     * @hide
+     */
     @NonNull
     public IBinder getToken() {
         return mToken;
@@ -185,6 +185,12 @@
         return mCreateCredentialRequest;
     }
 
+    /** Returns the request token matching the app request that should be cancelled. */
+    @NonNull
+    public RequestToken getRequestToken() {
+        return new RequestToken(mToken);
+    }
+
     /**
      * Returns default provider identifiers (component or package name) configured from the user
      * settings.
@@ -215,12 +221,31 @@
         return mGetCredentialRequest;
     }
 
+    /**
+     * Returns true if all options should be immediately displayed in the UI, and false otherwise.
+     *
+     * Normally this bit is set to false, upon which the selection UI should first display a
+     * condensed view of popular, deduplicated options that is determined based on signals like
+     * last-used timestamps, credential type priorities, and preferred providers configured from the
+     * user settings {@link #getDefaultProviderIds()}; at the same time, the UI should offer an
+     * option (button) that navigates the user to viewing all options from this condensed view.
+     *
+     * In some special occasions, e.g. when a request is initiated from the autofill drop-down
+     * suggestion, this bit will be set to true to indicate that the selection UI should immediately
+     * render the all option UI. This means that the request initiator has collected a user signal
+     * to confirm that the user wants to view all the available options at once.
+     */
+    public boolean isShowAllOptionsRequested() {
+        return mIsShowAllOptionsRequested;
+    }
+
     private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
             @NonNull String appPackageName,
             @Nullable CreateCredentialRequest createCredentialRequest,
             @Nullable GetCredentialRequest getCredentialRequest,
             boolean hasPermissionToOverrideDefault,
-            @NonNull List<String> defaultProviderIds) {
+            @NonNull List<String> defaultProviderIds,
+            boolean isShowAllOptionsRequested) {
         mToken = token;
         mType = type;
         mAppPackageName = appPackageName;
@@ -229,6 +254,7 @@
         mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
         mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
         mRegistryProviderIds = new ArrayList<>();
+        mIsShowAllOptionsRequested = isShowAllOptionsRequested;
     }
 
     private RequestInfo(@NonNull Parcel in) {
@@ -251,6 +277,7 @@
         mHasPermissionToOverrideDefault = in.readBoolean();
         mDefaultProviderIds = in.createStringArrayList();
         mRegistryProviderIds = in.createStringArrayList();
+        mIsShowAllOptionsRequested = in.readBoolean();
     }
 
     @Override
@@ -263,6 +290,7 @@
         dest.writeBoolean(mHasPermissionToOverrideDefault);
         dest.writeStringList(mDefaultProviderIds);
         dest.writeStringList(mRegistryProviderIds);
+        dest.writeBoolean(mIsShowAllOptionsRequested);
     }
 
     @Override
diff --git a/core/java/android/credentials/selection/RequestToken.java b/core/java/android/credentials/selection/RequestToken.java
new file mode 100644
index 0000000..27b83f8
--- /dev/null
+++ b/core/java/android/credentials/selection/RequestToken.java
@@ -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 android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.IBinder;
+
+/**
+ * Unique identifier for a getCredential / createCredential API session.
+ *
+ * To compare if two requests pertain to the same session, compare their RequestTokens using
+ * the {@link RequestToken#equals(Object)} method.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class RequestToken {
+
+    @NonNull
+    private final IBinder mToken;
+
+    /** @hide */
+    @TestApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+    public RequestToken(@NonNull IBinder token) {
+        mToken = token;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || !(obj instanceof RequestToken)) {
+            return false;
+        }
+        final RequestToken other = (RequestToken) obj;
+        return mToken.equals(other.mToken);
+    }
+
+    @Override
+    public int hashCode() {
+        return mToken.hashCode();
+    }
+}
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index cb1d3f5..3b7ade2 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -511,7 +511,7 @@
     Bundle getExtras();
 
     /**
-     * This is an out-of-band way for the the user of a cursor to communicate with the cursor. The
+     * This is an out-of-band way for the user of a cursor to communicate with the cursor. The
      * structure of each bundle is entirely defined by the cursor.
      *
      * <p>One use of this is to tell a cursor that it should retry its network request after it
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 35ae3c9..5dfeac7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -693,7 +693,7 @@
      * Begins a transaction in DEFERRED mode, with the android-specific constraint that the
      * transaction is read-only. The database may not be modified inside a read-only transaction.
      * <p>
-     * Read-only transactions may run concurrently with other read-only transactions, and if they
+     * Read-only transactions may run concurrently with other read-only transactions, and if the
      * database is in WAL mode, they may also run concurrently with IMMEDIATE or EXCLUSIVE
      * transactions.
      * <p>
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 0f66fcb..5cbf24f 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,16 +16,12 @@
 
 package android.ddm;
 
-import static com.android.internal.util.Preconditions.checkArgument;
-
 import android.util.Log;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import org.apache.harmony.dalvik.ddmc.Chunk;
 import org.apache.harmony.dalvik.ddmc.ChunkHandler;
 import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -35,10 +31,8 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
-import java.lang.reflect.Method;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 
 /**
  * Handle various requests related to profiling / debugging of the view system.
@@ -352,48 +346,17 @@
      *
      * The return value is encoded the same way as a single parameter (type + value)
      */
-    private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
+    private Chunk invokeViewMethod(View rootView, final View targetView, ByteBuffer in) {
         int l = in.getInt();
         String methodName = getString(in, l);
 
-        Class<?>[] argTypes;
-        Object[] args;
-        if (!in.hasRemaining()) {
-            argTypes = new Class<?>[0];
-            args = new Object[0];
-        } else {
-            int nArgs = in.getInt();
-            argTypes = new Class<?>[nArgs];
-            args = new Object[nArgs];
-
-            try {
-                deserializeMethodParameters(args, argTypes, in);
-            } catch (ViewMethodInvocationSerializationException e) {
-                return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
-            }
-        }
-
-        Method method;
         try {
-            method = targetView.getClass().getMethod(methodName, argTypes);
-        } catch (NoSuchMethodException e) {
-            Log.e(TAG, "No such method: " + e.getMessage());
-            return createFailChunk(ERR_INVALID_PARAM,
-                    "No such method: " + e.getMessage());
-        }
-
-        try {
-            Object result = ViewDebug.invokeViewMethod(targetView, method, args);
-            Class<?> returnType = method.getReturnType();
-            byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
+            byte[] returnValue =  ViewDebug.invokeViewMethod(targetView, methodName, in);
             return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
+        } catch (ViewDebug.ViewMethodInvocationSerializationException e) {
+            return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
         } catch (Exception e) {
-            Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
-            String msg = e.getCause().getMessage();
-            if (msg == null) {
-                msg = e.getCause().toString();
-            }
-            return createFailChunk(ERR_EXCEPTION, msg);
+            return createFailChunk(ERR_EXCEPTION, e.getMessage());
         }
     }
 
@@ -431,175 +394,4 @@
         byte[] data = b.toByteArray();
         return new Chunk(CHUNK_VUOP, data, 0, data.length);
     }
-
-    /**
-     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
-     * buffer.
-     *
-     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
-     * be the same length, and will be set to the argument types of the data read.
-     *
-     * @hide
-     */
-    @VisibleForTesting
-    public static void deserializeMethodParameters(
-            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
-            ViewMethodInvocationSerializationException {
-        checkArgument(args.length == argTypes.length);
-
-        for (int i = 0; i < args.length; i++) {
-            char typeSignature = in.getChar();
-            boolean isArray = typeSignature == SIG_ARRAY;
-            if (isArray) {
-                char arrayType = in.getChar();
-                if (arrayType != SIG_BYTE) {
-                    // This implementation only supports byte-arrays for now.
-                    throw new ViewMethodInvocationSerializationException(
-                            "Unsupported array parameter type (" + typeSignature
-                                    + ") to invoke view method @argument " + i);
-                }
-
-                int arrayLength = in.getInt();
-                if (arrayLength > in.remaining()) {
-                    // The sender did not actually sent the specified amount of bytes. This
-                    // avoids a malformed packet to trigger an out-of-memory error.
-                    throw new BufferUnderflowException();
-                }
-
-                byte[] byteArray = new byte[arrayLength];
-                in.get(byteArray);
-
-                argTypes[i] = byte[].class;
-                args[i] = byteArray;
-            } else {
-                switch (typeSignature) {
-                    case SIG_BOOLEAN:
-                        argTypes[i] = boolean.class;
-                        args[i] = in.get() != 0;
-                        break;
-                    case SIG_BYTE:
-                        argTypes[i] = byte.class;
-                        args[i] = in.get();
-                        break;
-                    case SIG_CHAR:
-                        argTypes[i] = char.class;
-                        args[i] = in.getChar();
-                        break;
-                    case SIG_SHORT:
-                        argTypes[i] = short.class;
-                        args[i] = in.getShort();
-                        break;
-                    case SIG_INT:
-                        argTypes[i] = int.class;
-                        args[i] = in.getInt();
-                        break;
-                    case SIG_LONG:
-                        argTypes[i] = long.class;
-                        args[i] = in.getLong();
-                        break;
-                    case SIG_FLOAT:
-                        argTypes[i] = float.class;
-                        args[i] = in.getFloat();
-                        break;
-                    case SIG_DOUBLE:
-                        argTypes[i] = double.class;
-                        args[i] = in.getDouble();
-                        break;
-                    case SIG_STRING: {
-                        argTypes[i] = String.class;
-                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
-                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
-                        in.get(rawStringBuffer);
-                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
-                        break;
-                    }
-                    default:
-                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
-                        throw new ViewMethodInvocationSerializationException(
-                                "Unsupported parameter type (" + typeSignature
-                                        + ") to invoke view method.");
-                }
-            }
-
-        }
-    }
-
-    /**
-     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
-     * @hide
-     */
-    @VisibleForTesting
-    public static byte[] serializeReturnValue(Class<?> type, Object value)
-            throws ViewMethodInvocationSerializationException, IOException {
-        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
-        DataOutputStream dos = new DataOutputStream(byteOutStream);
-
-        if (type.isArray()) {
-            if (!type.equals(byte[].class)) {
-                // Only byte arrays are supported currently.
-                throw new ViewMethodInvocationSerializationException(
-                        "Unsupported array return type (" + type + ")");
-            }
-            byte[] byteArray = (byte[]) value;
-            dos.writeChar(SIG_ARRAY);
-            dos.writeChar(SIG_BYTE);
-            dos.writeInt(byteArray.length);
-            dos.write(byteArray);
-        } else if (boolean.class.equals(type)) {
-            dos.writeChar(SIG_BOOLEAN);
-            dos.write((boolean) value ? 1 : 0);
-        } else if (byte.class.equals(type)) {
-            dos.writeChar(SIG_BYTE);
-            dos.writeByte((byte) value);
-        } else if (char.class.equals(type)) {
-            dos.writeChar(SIG_CHAR);
-            dos.writeChar((char) value);
-        } else if (short.class.equals(type)) {
-            dos.writeChar(SIG_SHORT);
-            dos.writeShort((short) value);
-        } else if (int.class.equals(type)) {
-            dos.writeChar(SIG_INT);
-            dos.writeInt((int) value);
-        } else if (long.class.equals(type)) {
-            dos.writeChar(SIG_LONG);
-            dos.writeLong((long) value);
-        } else if (double.class.equals(type)) {
-            dos.writeChar(SIG_DOUBLE);
-            dos.writeDouble((double) value);
-        } else if (float.class.equals(type)) {
-            dos.writeChar(SIG_FLOAT);
-            dos.writeFloat((float) value);
-        } else if (String.class.equals(type)) {
-            dos.writeChar(SIG_STRING);
-            dos.writeUTF(value != null ? (String) value : "");
-        } else {
-            dos.writeChar(SIG_VOID);
-        }
-
-        return byteOutStream.toByteArray();
-    }
-
-    // Prefixes for simple primitives. These match the JNI definitions.
-    private static final char SIG_ARRAY = '[';
-    private static final char SIG_BOOLEAN = 'Z';
-    private static final char SIG_BYTE = 'B';
-    private static final char SIG_SHORT = 'S';
-    private static final char SIG_CHAR = 'C';
-    private static final char SIG_INT = 'I';
-    private static final char SIG_LONG = 'J';
-    private static final char SIG_FLOAT = 'F';
-    private static final char SIG_DOUBLE = 'D';
-    private static final char SIG_VOID = 'V';
-    // Prefixes for some commonly used objects
-    private static final char SIG_STRING = 'R';
-
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    public static class ViewMethodInvocationSerializationException extends Exception {
-        ViewMethodInvocationSerializationException(String message) {
-            super(message);
-        }
-    }
 }
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index d51e62e..1488cff 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -15,6 +15,8 @@
  */
 package android.hardware.biometrics;
 
+import android.hardware.biometrics.BiometricSourceType;
+
 /**
  * Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system
  * services (e.g. SystemUI) to register a listener for updates about the current state of biometric
@@ -49,4 +51,15 @@
      * @param userId The user Id for the requested authentication
      */
     void onAuthenticationFailed(int requestReason, int userId);
+
+    /**
+     * Defines behavior in response to biometric being acquired.
+     * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+     * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+     * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+     *                     a known acquired message.
+     */
+    void onAuthenticationAcquired(
+        in BiometricSourceType biometricSourceType, int requestReason, int acquiredInfo
+    );
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 665d8d2..451d6fb 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5313,7 +5313,7 @@
      * </code></pre>
      * <ul>
      * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
-     * <li>AE_TARGET_FPS_RANGE: {{<em>, 30}, {</em>, 60}}</li>
+     * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
      * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
      * </ul>
      * <p>This key is available on all devices.</p>
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 1867a17..7abe821 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -624,6 +624,120 @@
     }
 
     /**
+     * Gets an extension specific camera characteristics field value.
+     *
+     * <p>An extension can have a reduced set of camera capabilities (such as limited zoom ratio
+     * range, available video stabilization modes, etc). This API enables applications to query for
+     * an extension’s specific camera characteristics. Applications are recommended to prioritize
+     * obtaining camera characteristics using this API when using an extension. A {@code null}
+     * result indicates that the extension specific characteristic is not defined or available.
+     *
+     * @param extension The extension type.
+     * @param key The characteristics field to read.
+     * @return The value of that key, or {@code null} if the field is not set.
+     *
+     * @throws IllegalArgumentException if the key is not valid or extension type is not a supported
+     * device-specific extension.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+    public <T> @Nullable T get(@Extension int extension,
+            @NonNull CameraCharacteristics.Key<T> key) {
+        final IBinder token = new Binder(TAG + "#get:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            if (areAdvancedExtensionsSupported() && getKeys(extension).contains(key)) {
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId, mCharacteristicsMapNative);
+                CameraMetadataNative metadata =
+                        extender.getAvailableCharacteristicsKeyValues(mCameraId);
+                CameraCharacteristics fallbackCharacteristics = mCharacteristicsMap.get(mCameraId);
+                if (metadata == null) {
+                    return fallbackCharacteristics.get(key);
+                }
+                CameraCharacteristics characteristics = new CameraCharacteristics(metadata);
+                T value = characteristics.get(key);
+                return value == null ? fallbackCharacteristics.get(key) : value;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension for the specified key! Extension "
+                    + "service does not respond!");
+        } finally {
+            unregisterClient(mContext, token);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the {@link CameraCharacteristics} keys that have extension-specific values.
+     *
+     * <p>An application can query the value from the key using
+     * {@link #get(int, CameraCharacteristics.Key)} API.
+     *
+     * @param extension The extension type.
+     * @return An unmodifiable set of keys that are extension specific.
+     *
+     * @throws IllegalArgumentException in case the extension type is not a
+     * supported device-specific extension
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+    public @NonNull Set<CameraCharacteristics.Key> getKeys(@Extension int extension) {
+        final IBinder token =
+                new Binder(TAG + "#getKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        HashSet<CameraCharacteristics.Key> ret = new HashSet<>();
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            if (areAdvancedExtensionsSupported()) {
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId, mCharacteristicsMapNative);
+                CameraMetadataNative metadata =
+                        extender.getAvailableCharacteristicsKeyValues(mCameraId);
+                if (metadata == null) {
+                    return Collections.emptySet();
+                }
+
+                int[] keys = metadata.get(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+                if (keys == null) {
+                    throw new AssertionError(
+                            "android.request.availableCharacteristicsKeys must be non-null"
+                                    + " in the characteristics");
+                }
+                CameraCharacteristics chars = new CameraCharacteristics(metadata);
+
+                Object key = CameraCharacteristics.Key.class;
+                Class<CameraCharacteristics.Key<?>> keyTyped =
+                        (Class<CameraCharacteristics.Key<?>>) key;
+
+                ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
+                        /*includeSynthetic*/ true));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension for all available keys! Extension "
+                    + "service does not respond!");
+        } finally {
+            unregisterClient(mContext, token);
+        }
+        return Collections.unmodifiableSet(ret);
+    }
+
+    /**
      * Checks for postview support of still capture.
      *
      * <p>A postview is a preview version of the still capture that is available before the final
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index fb2df54..6653577 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureCallback;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Size;
 
 import com.android.internal.camera.flags.Flags;
@@ -56,6 +57,7 @@
     private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
     private final CameraManager mCameraManager;
 
+    private CameraUsageTracker mCameraUsageTracker;
     private static final String TAG = "AdvancedExtender";
 
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -81,6 +83,10 @@
         }
     }
 
+    void setCameraUsageTracker(CameraUsageTracker tracker) {
+        mCameraUsageTracker = tracker;
+    }
+
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public long getMetadataVendorId(@NonNull String cameraId) {
         long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ?
@@ -222,6 +228,23 @@
     public abstract List<CaptureResult.Key> getAvailableCaptureResultKeys(
             @NonNull String cameraId);
 
+    /**
+     * Returns a list of {@link CameraCharacteristics} key/value pairs for apps to use when
+     * querying the Extensions specific {@link CameraCharacteristics}.
+     *
+     * <p>To ensure the correct {@link CameraCharacteristics} are used when an extension is
+     * enabled, an application should prioritize the value returned from the list if the
+     * {@link CameraCharacteristics} key is present. If the key doesn't exist in the returned list,
+     * then the application should query the value using
+     * {@link CameraCharacteristics#get(CameraCharacteristics.Key)}.
+     *
+     * <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
+     * a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+    @NonNull
+    public abstract List<Pair<CameraCharacteristics.Key, Object>>
+            getAvailableCharacteristicsKeyValues();
 
     private final class AdvancedExtenderImpl extends IAdvancedExtenderImpl.Stub {
         @Override
@@ -264,7 +287,9 @@
 
         @Override
         public ISessionProcessorImpl getSessionProcessor() {
-            return AdvancedExtender.this.getSessionProcessor().getSessionProcessorBinder();
+            SessionProcessor processor =AdvancedExtender.this.getSessionProcessor();
+            processor.setCameraUsageTracker(mCameraUsageTracker);
+            return processor.getSessionProcessorBinder();
         }
 
         @Override
@@ -322,6 +347,33 @@
             // Feature is currently unsupported
             return false;
         }
+
+        @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
+        @Override
+        public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) {
+            List<Pair<CameraCharacteristics.Key, Object>> entries =
+                    AdvancedExtender.this.getAvailableCharacteristicsKeyValues();
+
+            if ((entries != null) && !entries.isEmpty()) {
+                CameraMetadataNative ret = new CameraMetadataNative();
+                long vendorId = mMetadataVendorIdMap.containsKey(cameraId)
+                        ? mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE;
+                ret.setVendorId(vendorId);
+                int[] characteristicsKeyTags = new int[entries.size()];
+                int i = 0;
+                for (Pair<CameraCharacteristics.Key, Object> entry : entries) {
+                    int tag = CameraMetadataNative.getTag(entry.first.getName(), vendorId);
+                    characteristicsKeyTags[i++] = tag;
+                    ret.set(entry.first, entry.second);
+                }
+                ret.set(CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+                        characteristicsKeyTags);
+
+                return ret;
+            }
+
+            return null;
+        }
     }
 
     @NonNull IAdvancedExtenderImpl getAdvancedExtenderBinder() {
diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
index 1426d7b..fa0d14a 100644
--- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java
+++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.AppOpsManager;
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
@@ -29,6 +30,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.camera.flags.Flags;
 
+interface CameraUsageTracker {
+    void startCameraOperation();
+    void finishCameraOperation();
+}
+
 /**
  * Base service class that extension service implementations must extend.
  *
@@ -38,8 +44,33 @@
 @FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public abstract class CameraExtensionService extends Service {
     private static final String TAG = "CameraExtensionService";
+    private CameraUsageTracker mCameraUsageTracker;
     private static Object mLock = new Object();
 
+    private final class CameraTracker implements CameraUsageTracker {
+
+        private final AppOpsManager mAppOpsService = getApplicationContext().getSystemService(
+                AppOpsManager.class);
+        private final String mPackageName = getPackageName();
+        private final String mAttributionTag = getAttributionTag();
+        private int mUid = getApplicationInfo().uid;
+
+        @Override
+        public void startCameraOperation() {
+            if (mAppOpsService != null) {
+                mAppOpsService.startOp(AppOpsManager.OPSTR_CAMERA, mUid, mPackageName,
+                        mAttributionTag, "Camera extensions");
+            }
+        }
+
+        @Override
+        public void finishCameraOperation() {
+            if (mAppOpsService != null) {
+                mAppOpsService.finishOp(AppOpsManager.OPSTR_CAMERA, mUid, mPackageName,
+                        mAttributionTag);
+            }
+        }
+    }
     @GuardedBy("mLock")
     private static IInitializeSessionCallback mInitializeCb = null;
 
@@ -49,16 +80,22 @@
             synchronized (mLock) {
                 mInitializeCb = null;
             }
+            if (mCameraUsageTracker != null) {
+                mCameraUsageTracker.finishCameraOperation();
+            }
         }
     };
 
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    protected CameraExtensionService() {}
+    protected CameraExtensionService() { }
 
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @Override
     @NonNull
     public IBinder onBind(@Nullable Intent intent) {
+        if (mCameraUsageTracker == null) {
+            mCameraUsageTracker = new CameraTracker();
+        }
         return new CameraExtensionServiceImpl();
     }
 
@@ -132,8 +169,10 @@
         @Override
         public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
                 throws RemoteException {
-            return CameraExtensionService.this.onInitializeAdvancedExtension(
-                    extensionType).getAdvancedExtenderBinder();
+            AdvancedExtender extender =  CameraExtensionService.this.onInitializeAdvancedExtension(
+                    extensionType);
+            extender.setCameraUsageTracker(mCameraUsageTracker);
+            return extender.getAdvancedExtenderBinder();
         }
     }
 
diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
index 101442f..3071f0d 100644
--- a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
@@ -38,4 +38,5 @@
     CameraMetadataNative getAvailableCaptureResultKeys(in String cameraId);
     boolean isCaptureProcessProgressAvailable();
     boolean isPostviewAvailable();
+    CameraMetadataNative getAvailableCharacteristicsKeyValues(in String cameraId);
 }
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
index 6ed0c14..9c5136b 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -76,10 +76,15 @@
 @FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public abstract class SessionProcessor {
     private static final String TAG = "SessionProcessor";
+    private CameraUsageTracker mCameraUsageTracker;
 
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     protected SessionProcessor() {}
 
+    void setCameraUsageTracker(CameraUsageTracker tracker) {
+        mCameraUsageTracker = tracker;
+    }
+
     /**
      * Callback for notifying the status of {@link
      * #startCapture} and {@link #startRepeating}.
@@ -379,12 +384,18 @@
         @Override
         public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, String statsKey)
                 throws RemoteException {
+            if (mCameraUsageTracker != null) {
+                mCameraUsageTracker.startCameraOperation();
+            }
             SessionProcessor.this.onCaptureSessionStart(
                     new RequestProcessor(requestProcessor, mVendorId), statsKey);
         }
 
         @Override
         public void onCaptureSessionEnd() throws RemoteException {
+            if (mCameraUsageTracker != null) {
+                mCameraUsageTracker.finishCameraOperation();
+            }
             SessionProcessor.this.onCaptureSessionEnd();
         }
 
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 64a62a9..f18a0b7 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -202,8 +202,11 @@
     /**
      * Called by the window manager to perform traversals while holding a
      * surface flinger transaction.
+     * @param t The default transaction.
+     * @param displayTransactions The transactions mapped by display id.
      */
-    public abstract void performTraversal(Transaction t);
+    public abstract void performTraversal(Transaction t,
+            SparseArray<SurfaceControl.Transaction> displayTransactions);
 
     /**
      * Tells the display manager about properties of the display that depend on the windows on it.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 7bea9ae..1f54959 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -67,6 +67,9 @@
 
     KeyCharacterMap getKeyCharacterMap(String layoutDescriptor);
 
+    // Returns the mouse pointer speed.
+    int getMousePointerSpeed();
+
     // Temporarily changes the pointer speed.
     void tryPointerSpeed(int speed);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 4ebbde7..744dfae9 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,9 +16,11 @@
 
 package android.hardware.input;
 
+import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
 import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -26,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
@@ -294,6 +297,23 @@
     }
 
     /**
+     * Gets the {@link InputDevice.ViewBehavior} of the input device with a given {@code id}.
+     *
+     * <p>Use this API to query a fresh view behavior instance whenever the input device
+     * changes.
+     *
+     * @param deviceId the id of the input device whose view behavior is being requested.
+     * @return the view behavior of the input device with the provided id, or {@code null} if there
+     *      is not input device with the provided id.
+     */
+    @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+    @Nullable
+    public InputDevice.ViewBehavior getInputDeviceViewBehavior(int deviceId) {
+        InputDevice device = getInputDevice(deviceId);
+        return device == null ? null : device.getViewBehavior();
+    }
+
+    /**
      * Gets information about the input device with the specified descriptor.
      * @param descriptor The input device descriptor.
      * @return The input device or null if not found.
@@ -838,6 +858,28 @@
     }
 
     /**
+     * Returns the mouse pointer speed.
+     *
+     * <p>The pointer speed is a value between {@link InputSettings#MIN_POINTER_SPEED} and
+     * {@link InputSettings#MAX_POINTER_SPEED}, the default value being
+     * {@link InputSettings#DEFAULT_POINTER_SPEED}.
+     *
+     * <p> Note that while setting the mouse pointer speed, it's possible that the input reader has
+     * only received this value and has not yet completed reconfiguring itself with this value.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // TestApi without associated feature.
+    @TestApi
+    public int getMousePointerSpeed() {
+        try {
+            return mIm.getMousePointerSpeed();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Changes the mouse pointer speed temporarily, but does not save the setting.
      * <p>
      * Requires {@link android.Manifest.permission#SET_POINTER_SPEED}.
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 89fa5fb..54e34ec 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -54,8 +54,8 @@
 
     /**
      * Pointer Speed: The default pointer speed (0).
-     * @hide
      */
+    @SuppressLint("UnflaggedApi") // TestApi without associated feature.
     public static final int DEFAULT_POINTER_SPEED = 0;
 
     /**
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index d943c37..1cc910c 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -219,26 +219,22 @@
             if (!glyphData.hasBaseText()) {
                 return;
             }
-            if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+            boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
+            mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+                    GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+                    mBaseTextPaint));
+            if (glyphData.hasValidShiftText()) {
                 mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
-                        GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
+                        GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+                        mModifierTextPaint));
+            }
+            if (glyphData.hasValidAltGrText()) {
                 mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
                         GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
-            } else if (glyphData.hasValidShiftText()) {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint));
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
-                        GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint));
-            } else if (glyphData.hasValidAltGrText()) {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
-            } else {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_CENTER, mBaseTextPaint));
+            }
+            if (glyphData.hasValidAltShiftText()) {
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
+                        GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
             }
         }
 
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 241c452..844e02f 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -23,8 +23,6 @@
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
-import java.util.Locale;
-
 /**
  * A complimentary class to {@link KeyboardLayoutPreviewDrawable} describing the physical key layout
  * of a Physical keyboard and provides information regarding the scan codes produced by the physical
@@ -339,9 +337,9 @@
         }
         int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
         if (Character.isValidCodePoint(utf8Char)) {
-            return String.valueOf(Character.toChars(utf8Char)).toUpperCase(Locale.getDefault());
+            return String.valueOf(Character.toChars(utf8Char));
         } else {
-            return String.valueOf(kcm.getDisplayLabel(keyCode)).toUpperCase(Locale.getDefault());
+            return String.valueOf(kcm.getDisplayLabel(keyCode));
         }
     }
 
@@ -398,13 +396,17 @@
         private final String mBaseText;
         private final String mShiftText;
         private final String mAltGrText;
+        private final String mAltGrShiftText;
 
         public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
-            mBaseText = getKeyText(kcm, keyCode, 0);
+            mBaseText = getKeyText(kcm, keyCode, KeyEvent.META_CAPS_LOCK_ON);
             mShiftText = getKeyText(kcm, keyCode,
                     KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
             mAltGrText = getKeyText(kcm, keyCode,
-                    KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON);
+                    KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_CAPS_LOCK_ON);
+            mAltGrShiftText = getKeyText(kcm, keyCode,
+                    KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_LEFT_ON
+                            | KeyEvent.META_SHIFT_ON);
         }
 
         public String getBaseText() {
@@ -419,6 +421,10 @@
             return mAltGrText;
         }
 
+        public String getAltGrShiftText() {
+            return mAltGrShiftText;
+        }
+
         public boolean hasBaseText() {
             return !TextUtils.isEmpty(mBaseText);
         }
@@ -430,5 +436,12 @@
         public boolean hasValidAltGrText() {
             return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
         }
+
+        public boolean hasValidAltShiftText() {
+            return !TextUtils.isEmpty(mAltGrShiftText)
+                    && !TextUtils.equals(mBaseText, mAltGrShiftText)
+                    && !TextUtils.equals(mAltGrText, mAltGrShiftText)
+                    && !TextUtils.equals(mShiftText, mAltGrShiftText);
+        }
     }
 }
diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS
index 302fdd7..51a85e4 100644
--- a/core/java/android/hardware/radio/OWNERS
+++ b/core/java/android/hardware/radio/OWNERS
@@ -1,4 +1,3 @@
 xuweilin@google.com
 oscarazu@google.com
 ericjeong@google.com
-keunyoung@google.com
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 4c95e02..a968c6f 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -62,55 +62,55 @@
 @SystemApi
 public final class ProgramSelector implements Parcelable {
     /** Invalid program type.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link IdentifierType} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_INVALID = 0;
     /** Analog AM radio (with or without RDS).
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link IdentifierType} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_AM = 1;
     /** analog FM radio (with or without RDS).
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link IdentifierType} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_FM = 2;
     /** AM HD Radio.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_AM_HD = 3;
     /** FM HD Radio.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_FM_HD = 4;
     /** Digital audio broadcasting.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_DAB = 5;
     /** Digital Radio Mondiale.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_DRMO = 6;
     /** SiriusXM Satellite Radio.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_SXM = 7;
     /** Vendor-specific, not synced across devices.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_VENDOR_START = 1000;
-    /** @deprecated use {@link ProgramIdentifier} instead */
+    /** @deprecated use {@link Identifier} instead */
     @Deprecated
     public static final int PROGRAM_TYPE_VENDOR_END = 1999;
     /**
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      * @removed mistakenly exposed previously
      */
     @Deprecated
@@ -271,7 +271,7 @@
      */
     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
     /**
-     * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+     * @see #IDENTIFIER_TYPE_DAB_SID_EXT
      *
      * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
@@ -381,7 +381,7 @@
      */
     public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
     /**
-     * @see {@link IDENTIFIER_TYPE_VENDOR_START}
+     * @see #IDENTIFIER_TYPE_VENDOR_START
      */
     public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
     /**
@@ -771,7 +771,7 @@
          * Returns whether this Identifier's type is considered a category when filtering
          * ProgramLists for category entries.
          *
-         * @see {@link ProgramList.Filter#areCategoriesIncluded()}
+         * @see ProgramList.Filter#areCategoriesIncluded
          * @return False if this identifier's type is not tuneable (e.g. DAB ensemble or
          *         vendor-specified type). True otherwise.
          */
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 41f21ef..61cf8901 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -311,7 +311,7 @@
         }
 
         /** Unique module identifier provided by the native service.
-         * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+         * For use with {@link #openTuner(int, BandConfig, boolean, RadioTuner.Callback, Handler)}.
          * @return the radio module unique identifier.
          */
         public int getId() {
@@ -1561,7 +1561,7 @@
         /** Main channel expressed in units according to band type.
          * Currently all defined band types express channels as frequency in kHz
          * @return the program channel
-         * @deprecated Use {@link getSelector()} instead.
+         * @deprecated Use {@link ProgramInfo#getSelector} instead.
          */
         @Deprecated
         public int getChannel() {
@@ -1575,7 +1575,7 @@
 
         /** Sub channel ID. E.g 1 for HD radio HD1
          * @return the program sub channel
-         * @deprecated Use {@link getSelector()} instead.
+         * @deprecated Use {@link ProgramInfo#getSelector} instead.
          */
         @Deprecated
         public int getSubChannel() {
@@ -1604,7 +1604,7 @@
 
         /** {@code true} if the received program is digital (e.g HD radio)
          * @return {@code true} if digital, {@code false} otherwise.
-         * @deprecated Use {@link getLogicallyTunedTo()} instead.
+         * @deprecated Use {@link ProgramInfo#getLogicallyTunedTo()} instead.
          */
         @Deprecated
         public boolean isDigital() {
@@ -1913,7 +1913,8 @@
      * Removes previously registered announcement listener.
      *
      * @param listener announcement listener, previously registered with
-     *        {@link addAnnouncementListener}
+     *        {@link #addAnnouncementListener(Executor, Set, Announcement.OnListUpdatedListener)}
+     *        or {@link #addAnnouncementListener(Set, Announcement.OnListUpdatedListener)}
      */
     @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index da6b9c2..67381ec 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -593,7 +593,7 @@
      * Helper for getting the String key used by {@link RadioMetadata} from the
      * corrsponding native integer key.
      *
-     * @param editorKey The key used by the editor
+     * @param nativeKey The key used by the editor
      * @return the key used by this class or null if no mapping exists
      * @hide
      */
@@ -743,11 +743,11 @@
          * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
          * METADATA_KEYs defined in this class are used they may only be one of the following:
          * <ul>
-         * <li>{@link #MEADATA_KEY_CLOCK}</li>
+         * <li>{@link #METADATA_KEY_CLOCK}</li>
          * </ul>
          *
          * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
-         * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
+         * @param timezoneOffsetMinutes Offset of timezone from UTC + 0 in minutes.
          * @return the same Builder instance.
          */
         public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 71698e4..281ee50 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1256,7 +1256,9 @@
     private void updateEditorToolTypeInternal(int toolType) {
         if (Flags.useHandwritingListenerForTooltype()) {
             mLastUsedToolType = toolType;
-            mInputEditorInfo.setInitialToolType(toolType);
+            if (mInputEditorInfo != null) {
+                mInputEditorInfo.setInitialToolType(toolType);
+            }
         }
         onUpdateEditorToolType(toolType);
     }
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index 0009e61..e0b631d 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -15,9 +15,11 @@
  */
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.pm.Flags;
 
 /**
  * Provides a way to register and obtain the system service binder objects managed by the ART
@@ -60,4 +62,18 @@
     public ServiceRegisterer getArtdServiceRegisterer() {
         return new ServiceRegisterer("artd");
     }
+
+    /** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+    public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
+        return new ServiceRegisterer("artd_pre_reboot");
+    }
+
+    /** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+    public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
+        return new ServiceRegisterer("dexopt_chroot_setup");
+    }
 }
diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java
index 4908919..87cd4ee 100644
--- a/core/java/android/os/ConfigUpdate.java
+++ b/core/java/android/os/ConfigUpdate.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -131,6 +132,23 @@
             "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
 
     /**
+     * Broadcast intent action indicating that the updated config data is available.
+     * This broadcast intent action is to be sent by the config updater app, and will be received
+     * and handled by the platform.
+     * <p>Extra: {@link #EXTRA_VERSION} the numeric version of the database.
+     * <p>Extra: {@link #EXTRA_REQUIRED_HASH} hash of the database, which is encoded by base-16
+     * SHA512
+     * <p>Extra: {@link #EXTRA_DOMAIN} the string identifying the affected module
+     * <p>Input: {@link android.content.Intent#getData} the URI to download config data file
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
+
+    /**
      * An integer to indicate the numeric version of the new data. Devices should only install
      * if the update version is newer than the current one.
      *
@@ -147,6 +165,16 @@
     @SystemApi
     public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
 
+    /**
+     * String identifying the affected module.
+     * Devices apply the updated config data to the module specified in the string.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final String EXTRA_DOMAIN  = "android.os.extra.DOMAIN";
+
     private ConfigUpdate() {
     }
 }
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 536ef31..a459aaa 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -412,7 +413,9 @@
      * Returns the base directory for per-user system directory, device encrypted.
      * {@hide}
      */
-    public static File getDataSystemDeDirectory() {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
+    public static @NonNull File getDataSystemDeDirectory() {
         return buildPath(getDataDirectory(), "system_de");
     }
 
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index b7649ba..650aead 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -17,6 +17,8 @@
 package android.os;
 
 import android.content.ComponentName;
+import android.os.Bundle;
+import android.content.pm.SignedPackageParcel;
 
 /**
   * Binder interface to query SystemConfig in the system server.
@@ -57,4 +59,14 @@
      * @see SystemConfigManager#getPreventUserDisablePackages
      */
     List<String> getPreventUserDisablePackages();
+
+    /**
+     * @see SystemConfigManager#getEnhancedConfirmationTrustedPackages
+     */
+    List<SignedPackageParcel> getEnhancedConfirmationTrustedPackages();
+
+    /**
+     * @see SystemConfigManager#getEnhancedConfirmationTrustedInstallers
+     */
+    List<SignedPackageParcel> getEnhancedConfirmationTrustedInstallers();
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c0d1fb9..800ba6d 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -150,4 +150,5 @@
     void setBootUser(int userId);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
     int getBootUser();
+    int[] getProfileIdsExcludingHidden(int userId, boolean enabledOnly);
 }
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e6bfcd7..224b10d 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -313,25 +313,33 @@
          * close to the target duration.
          *
          * @param workDuration the work duration of each component.
-         * @throws IllegalArgumentException if work period start timestamp is not positive, or
-         *         actual total duration is not positive, or actual CPU duration is not positive,
-         *         or actual GPU duration is negative.
+         * @throws IllegalArgumentException if
+         * the work period start timestamp or the total duration are less than or equal to zero,
+         * if either the actual CPU duration or actual GPU duration is less than zero,
+         * or if both the CPU and GPU durations are zero.
          */
         @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
         public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
             if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
                 throw new IllegalArgumentException(
-                    "the work period start timestamp should be positive.");
+                    "the work period start timestamp should be greater than zero.");
             }
             if (workDuration.mActualTotalDurationNanos <= 0) {
-                throw new IllegalArgumentException("the actual total duration should be positive.");
+                throw new IllegalArgumentException(
+                    "the actual total duration should be greater than zero.");
             }
-            if (workDuration.mActualCpuDurationNanos <= 0) {
-                throw new IllegalArgumentException("the actual CPU duration should be positive.");
+            if (workDuration.mActualCpuDurationNanos < 0) {
+                throw new IllegalArgumentException(
+                    "the actual CPU duration should be greater than or equal to zero.");
             }
             if (workDuration.mActualGpuDurationNanos < 0) {
                 throw new IllegalArgumentException(
-                    "the actual GPU duration should be non negative.");
+                    "the actual GPU duration should be greater than or equal to zero.");
+            }
+            if (workDuration.mActualCpuDurationNanos + workDuration.mActualGpuDurationNanos <= 0) {
+                throw new IllegalArgumentException(
+                    "either the actual CPU duration or the actual GPU duration should be greater"
+                    + "than zero.");
             }
             nativeReportActualWorkDuration(mNativeSessionPtr,
                     workDuration.mWorkPeriodStartTimestampNanos,
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 21ffbf1..13bc398 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -16,12 +16,15 @@
 package android.os;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.SignedPackage;
+import android.content.pm.SignedPackageParcel;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -29,6 +32,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 
 /**
@@ -175,4 +179,69 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+
+    /**
+     * Returns a set of signed packages, represented as (packageName, certificateDigest) pairs, that
+     * should be considered "trusted packages" by ECM (Enhanced Confirmation Mode).
+     *
+     * <p>"Trusted packages" are exempt from ECM (i.e., they will never be considered "restricted").
+     *
+     * <p>A package will be considered "trusted package" if and only if it *matches* least one of
+     * the (*packageName*, *certificateDigest*) pairs in this set, where *matches* means satisfying
+     * both of the following:
+     *
+     * <ol>
+     *   <li>The package's name equals *packageName*
+     *   <li>The package is, or was ever, signed by *certificateDigest*, according to the package's
+     *       {@link android.content.pm.SigningDetails}
+     * </ol>
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    @RequiresPermission(Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES)
+    @NonNull
+    public Set<SignedPackage> getEnhancedConfirmationTrustedPackages() {
+        try {
+            List<SignedPackageParcel> parcels = mInterface.getEnhancedConfirmationTrustedPackages();
+            return parcels.stream().map(SignedPackage::new).collect(Collectors.toSet());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a set of signed packages, represented as (packageName, certificateDigest) pairs, that
+     * should be considered "trusted installers" by ECM (Enhanced Confirmation Mode).
+     *
+     * <p>"Trusted installers", and all apps installed by a trusted installer, are exempt from ECM
+     * (i.e., they will never be considered "restricted").
+     *
+     * <p>A package will be considered a "trusted installer" if and only if it *matches* least one
+     * of the (*packageName*, *certificateDigest*) pairs in this set, where *matches* means
+     * satisfying both of the following:
+     *
+     * <ol>
+     *   <li>The package's name equals *packageName*
+     *   <li>The package is, or was ever, signed by *certificateDigest*, according to the package's
+     *       {@link android.content.pm.SigningDetails}
+     * </ol>
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    @RequiresPermission(Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES)
+    @NonNull
+    public Set<SignedPackage> getEnhancedConfirmationTrustedInstallers() {
+        try {
+            List<SignedPackageParcel> parcels =
+                    mInterface.getEnhancedConfirmationTrustedInstallers();
+            return parcels.stream().map(SignedPackage::new).collect(Collectors.toSet());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d6df8d9..0da19df 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3438,6 +3438,7 @@
     }
 
     /**
+     * @see #isVisibleBackgroundUsersSupported()
      * @hide
      */
     public static boolean isVisibleBackgroundUsersEnabled() {
@@ -3447,14 +3448,21 @@
     }
 
     /**
-     * Returns whether the device allows (full) users to be started in background visible in a given
+     * Returns whether the device allows full users to be started in background visible in a given
      * display (which would allow them to launch activities in that display).
      *
-     * @return {@code false} for most devices, except on automotive builds for vehiches with
+     * Note that this is specifically about allowing <b>full</b> users to be background visible.
+     * Even if it is false, there can still be background visible users.
+     *
+     * In particular, the Communal Profile is a background visible user, and it can be supported
+     * unrelated to the value of this method.
+     *
+     * @return {@code false} for most devices, except on automotive builds for vehicles with
      * passenger displays.
      *
      * @hide
      */
+    // TODO(b/310249114): Rename to isVisibleBackgroundFullUsersSupported
     @TestApi
     public boolean isVisibleBackgroundUsersSupported() {
         return isVisibleBackgroundUsersEnabled();
@@ -3470,12 +3478,13 @@
     }
 
     /**
-     * Returns whether the device allows (full) users to be started in background visible in the
+     * Returns whether the device allows full users to be started in background visible in the
      * {@link android.view.Display#DEFAULT_DISPLAY default display}.
      *
      * @return {@code false} for most devices, except passenger-only automotive build (i.e., when
      * Android runs in a separate system in the back seat to manage the passenger displays).
      *
+     * @see #isVisibleBackgroundUsersSupported()
      * @hide
      */
     @TestApi
@@ -5348,6 +5357,25 @@
     }
 
     /**
+     * @return A list of ids of profiles associated with the specified user excluding those with
+     * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+     * the user itself.
+     * @hide
+     * @see #getProfileIds(int, boolean)
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS}, conditional = true)
+    public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabled) {
+        try {
+            return mService.getProfileIdsExcludingHidden(userId, enabled);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the device credential owner id of the profile from
      * which this method is called, or userId if called from a user that
      * is not a profile.
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5078dc35..46705a3 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -29,6 +29,7 @@
 /**
  * Encapsulates a collection of attributes describing information about a vibration.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class VibrationAttributes implements Parcelable {
     private static final String TAG = "VibrationAttributes";
 
@@ -463,6 +464,7 @@
      * Builder class for {@link VibrationAttributes} objects.
      * By default, all information is set to UNKNOWN.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class Builder {
         private int mUsage = USAGE_UNKNOWN;
         private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
index 2ebcd83..5a54e90 100644
--- a/core/java/android/os/WorkDuration.java
+++ b/core/java/android/os/WorkDuration.java
@@ -83,7 +83,7 @@
     public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
         if (workPeriodStartTimestampNanos <= 0) {
             throw new IllegalArgumentException(
-                "the work period start timestamp should be positive.");
+                "the work period start timestamp should be greater than zero.");
         }
         mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
     }
@@ -95,7 +95,8 @@
      */
     public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
         if (actualTotalDurationNanos <= 0) {
-            throw new IllegalArgumentException("the actual total duration should be positive.");
+            throw new IllegalArgumentException(
+                "the actual total duration should be greater than zero.");
         }
         mActualTotalDurationNanos = actualTotalDurationNanos;
     }
@@ -106,8 +107,9 @@
      * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
-        if (actualCpuDurationNanos <= 0) {
-            throw new IllegalArgumentException("the actual CPU duration should be positive.");
+        if (actualCpuDurationNanos < 0) {
+            throw new IllegalArgumentException(
+                "the actual CPU duration should be greater than or equal to zero.");
         }
         mActualCpuDurationNanos = actualCpuDurationNanos;
     }
@@ -119,7 +121,8 @@
      */
     public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
         if (actualGpuDurationNanos < 0) {
-            throw new IllegalArgumentException("the actual GPU duration should be non negative.");
+            throw new IllegalArgumentException(
+                "the actual GPU duration should be greater than or equal to zero.");
         }
         mActualGpuDurationNanos = actualGpuDurationNanos;
     }
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index f3496e7..b1ef05a 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -71,7 +71,7 @@
  */
 public class ZygoteProcess {
 
-    private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+    private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 60000;
 
     /**
      * Use a relatively short delay, because for app zygote, this is in the critical path of
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 82518bf..6c728a4 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -114,3 +114,11 @@
     is_fixed_read_only: true
     bug: "309792384"
 }
+
+flag {
+    name: "storage_lifetime_api"
+    namespace: "phoenix"
+    description: "Feature flag for adding storage component health APIs."
+    is_fixed_read_only: true
+    bug: "309792384"
+}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 54ed73c..1ab48a2 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -175,4 +175,12 @@
     void setCloudMediaProvider(in String authority) = 96;
     String getCloudMediaProvider() = 97;
     long getInternalStorageBlockDeviceSize() = 98;
-}
\ No newline at end of file
+    /**
+     * Returns the remaining lifetime of the internal storage device, as an
+     * integer percentage. For example, 90 indicates that 90% of the storage
+     * device's useful lifetime remains. If no information is available, -1
+     * is returned.
+     */
+    @EnforcePermission("READ_PRIVILEGED_PHONE_STATE")
+    int getInternalStorageRemainingLifetime() = 99;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 3a57e84..5a09541 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -28,6 +28,7 @@
 
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -58,6 +59,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.Flags;
 import android.os.Handler;
 import android.os.IInstalld;
 import android.os.IVold;
@@ -2939,4 +2941,24 @@
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final int CRYPT_TYPE_DEFAULT = 1;
+
+    /**
+     * Returns the remaining lifetime of the internal storage device, as an integer percentage. For
+     * example, 90 indicates that 90% of the storage device's useful lifetime remains. If no
+     * information is available, -1 is returned.
+     *
+     * @return Percentage of the remaining useful lifetime of the internal storage device.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_STORAGE_LIFETIME_API)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getInternalStorageRemainingLifetime() {
+        try {
+            return mStorageManager.getInternalStorageRemainingLifetime();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
index ecde699..bef6456 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -247,6 +248,44 @@
     }
 
     /**
+     * Updates a contact key entry's local verification state that belongs to the app identified
+     * by ownerPackageName.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param ownerPackageName the package name of the app that owns the key
+     * @param localVerificationState the new local verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+            android.Manifest.permission.WRITE_CONTACTS})
+    public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull String ownerPackageName,
+            @VerificationState int localVerificationState) {
+        validateVerificationState(localVerificationState);
+
+        final Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+        extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+        final Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
      * Updates a contact key entry's remote verification state that belongs to the caller app.
      *
      * @param lookupKey the value that references the contact
@@ -275,6 +314,45 @@
         return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
     }
 
+    /**
+     * Updates a contact key entry's remote verification state that belongs to the app identified
+     * by ownerPackageName.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param ownerPackageName the package name of the app that owns the key
+     * @param remoteVerificationState the new remote verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+            android.Manifest.permission.WRITE_CONTACTS})
+    public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull String ownerPackageName,
+            @VerificationState int remoteVerificationState) {
+        validateVerificationState(remoteVerificationState);
+
+        final Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+        final Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+
     private static void validateVerificationState(int verificationState) {
         if (verificationState != UNVERIFIED
                 && verificationState != VERIFICATION_FAILED
@@ -297,12 +375,12 @@
     public boolean removeContactKey(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId) {
-        Bundle extras = new Bundle();
+        final Bundle extras = new Bundle();
         extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
         extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
         extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
 
-        Bundle response = nullSafeCall(mContentResolver,
+        final Bundle response = nullSafeCall(mContentResolver,
                 ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
 
         return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
@@ -369,6 +447,41 @@
     }
 
     /**
+     * Updates a self key entry's remote verification state that belongs to the app identified
+     * by ownerPackageName.
+     *
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param ownerPackageName the package name of the app that owns the key
+     * @param remoteVerificationState the new remote verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+            android.Manifest.permission.WRITE_CONTACTS})
+    public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull String ownerPackageName,
+            @VerificationState int remoteVerificationState) {
+        validateVerificationState(remoteVerificationState);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
      * Maximum size of a contact key.
      */
     public static int getMaxKeySizeBytes() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 524b733..ef2d5eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6015,8 +6015,10 @@
          *   +7 = fastest
          * @hide
          */
+        @SuppressLint({"NoSettingsProvider", "UnflaggedApi"}) // TestApi without associated feature.
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         @Readable
+        @TestApi
         public static final String POINTER_SPEED = "pointer_speed";
 
         /**
@@ -18159,6 +18161,16 @@
                 "show_notification_channel_warnings";
 
         /**
+         * Whether to disable app and notification screen share protections.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        @Readable
+        public static final String DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS =
+                "disable_screen_share_protections_for_apps_and_notifications";
+
+        /**
          * Whether cell is enabled/disabled
          * @hide
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2841dc0..658cec8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4943,6 +4943,13 @@
         public static final String COLUMN_IS_NTN = "is_ntn";
 
         /**
+         * TelephonyProvider column name for transferred status
+         *
+         * @hide
+         */
+        public static final String COLUMN_TRANSFER_STATUS = "transfer_status";
+
+        /**
          * TelephonyProvider column name to indicate the service capability bitmasks.
          *
          * @hide
@@ -5021,7 +5028,8 @@
                 COLUMN_SATELLITE_ENABLED,
                 COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                 COLUMN_IS_NTN,
-                COLUMN_SERVICE_CAPABILITIES
+                COLUMN_SERVICE_CAPABILITIES,
+                COLUMN_TRANSFER_STATUS
         );
 
         /**
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 298bdb8..e6a84df 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -600,6 +600,14 @@
      */
     public static final String EXTRA_ERROR = "error";
 
+    /**
+     * Name of the key used to mark whether the fill response is for a webview.
+     *
+     * @hide
+     */
+    public static final String WEBVIEW_REQUESTED_CREDENTIAL_KEY = "webview_requested_credential";
+
+
     private final IAutoFillService mInterface = new IAutoFillService.Stub() {
         @Override
         public void onConnectedStateChanged(boolean connected) {
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 5978383..8aeaacf 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -7,3 +7,9 @@
   bug: "268089816"
 }
 
+flag {
+  name: "chooser_payload_toggling"
+  namespace: "intentresolver"
+  description: "This flag controls content toggling in Chooser"
+  bug: "302691505"
+}
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
new file mode 100644
index 0000000..463adf4
--- /dev/null
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -0,0 +1,3 @@
+srazdan@google.com
+volnov@google.com
+hackz@google.com
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a813a3..d39c4ce 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -19,12 +19,15 @@
 import android.annotation.FlaggedApi;
 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.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
@@ -73,9 +76,15 @@
  * Limit API access to only carrier apps with certain permissions or apps running on
  * privileged UID.
  *
+ * TelephonyRegistryManager is intended for use on devices that implement
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+ * that do not implement this feature, the behavior is not reliable.
+ *
  * @hide
  */
 @SystemApi
+@SystemService(Context.TELEPHONY_REGISTRY_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY)
 @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
 public class TelephonyRegistryManager {
 
@@ -389,10 +398,11 @@
     }
 
     /**
-     * Notify call state changed on all subscriptions.
+     * Notify call state changed on all subscriptions, excluding over-the-top VOIP calls (otherwise
+     * known as self-managed calls in the Android Platform).
      *
      * @param state latest call state. e.g, offhook, ringing
-     * @param incomingNumber incoming phone number.
+     * @param incomingNumber incoming phone number or null in the case for OTT VOIP calls
      * @hide
      */
     @SystemApi
@@ -627,17 +637,20 @@
     }
 
     /**
-     * Notify outgoing emergency call.
+     * Notify outgoing emergency call to all applications that have registered a listener
+     * ({@link PhoneStateListener}) or a callback ({@link TelephonyCallback}) to monitor changes in
+     * telephony states.
      * @param simSlotIndex Sender phone ID.
-     * @param subId Sender subscription ID.
+     * @param subscriptionId Sender subscription ID.
      * @param emergencyNumber Emergency number.
      * @hide
      */
     @SystemApi
-    public void notifyOutgoingEmergencyCall(int simSlotIndex, int subId,
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void notifyOutgoingEmergencyCall(int simSlotIndex, int subscriptionId,
             @NonNull EmergencyNumber emergencyNumber) {
         try {
-            sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subId, emergencyNumber);
+            sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subscriptionId, emergencyNumber);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 0421d5a..32f05be 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -54,4 +54,11 @@
     public static boolean fixLineHeightForLocale() {
         return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
     }
+
+    /**
+     * @see Flags#icuBidiMigration()
+     */
+    public static boolean icuBidiMigration() {
+        return TextFlags.isFeatureEnabled(Flags.FLAG_ICU_BIDI_MIGRATION);
+    }
 }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1ea80f1..8ddb42d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,7 +18,7 @@
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -172,7 +172,7 @@
     /**
      * Value for justification mode indicating the text is justified by stretching letter spacing.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
             LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
 
@@ -1831,7 +1831,7 @@
      * @return the number of cluster count in the line.
      */
     @IntRange(from = 0)
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
             boolean includeTrailingWhitespace) {
         final int start = getLineStart(line);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index b268c2e..09f15c3 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -30,6 +30,7 @@
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.MeasuredText;
+import android.icu.text.Bidi;
 import android.text.AutoGrowArray.ByteArray;
 import android.text.AutoGrowArray.FloatArray;
 import android.text.AutoGrowArray.IntArray;
@@ -115,6 +116,11 @@
     // This is empty if mLtrWithoutBidi is true.
     private @NonNull ByteArray mLevels = new ByteArray();
 
+    // The bidi level for runs.
+    private @NonNull ByteArray mRunLevels = new ByteArray();
+
+    private Bidi mBidi;
+
     // The whole width of the text.
     // See getWholeWidth comments.
     private @FloatRange(from = 0.0f) float mWholeWidth;
@@ -148,6 +154,7 @@
         reset();
         mLevels.clearWithReleasingLargeArray();
         mWidths.clearWithReleasingLargeArray();
+        mRunLevels.clearWithReleasingLargeArray();
         mFontMetrics.clearWithReleasingLargeArray();
         mSpanEndCache.clearWithReleasingLargeArray();
     }
@@ -160,10 +167,12 @@
         mCopiedBuffer = null;
         mWholeWidth = 0;
         mLevels.clear();
+        mRunLevels.clear();
         mWidths.clear();
         mFontMetrics.clear();
         mSpanEndCache.clear();
         mMeasuredText = null;
+        mBidi = null;
     }
 
     /**
@@ -193,6 +202,13 @@
      * @hide
      */
     public @Layout.Direction int getParagraphDir() {
+        if (ClientFlags.icuBidiMigration()) {
+            if (mBidi == null) {
+                return Layout.DIR_LEFT_TO_RIGHT;
+            }
+            return (mBidi.getParaLevel() & 0x01) == 0
+                    ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+        }
         return mParaDir;
     }
 
@@ -204,6 +220,62 @@
      */
     public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
                                     @IntRange(from = 0) int end) {  // exclusive
+        if (ClientFlags.icuBidiMigration()) {
+            // Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
+            if (mBidi == null) {
+                return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+            }
+
+            // Easy case: If the original text only contains single directionality run, the
+            // substring is only single run.
+            if (start == end) {
+                if ((mBidi.getParaLevel() & 0x01) == 0) {
+                    return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+                } else {
+                    return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+                }
+            }
+
+            // Okay, now we need to generate the line instance.
+            Bidi bidi = mBidi.createLineBidi(start, end);
+
+            // Easy case: If the line instance only contains single directionality run, no need
+            // to reorder visually.
+            if (bidi.getRunCount() == 1) {
+                if ((bidi.getParaLevel() & 0x01) == 1) {
+                    return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+                } else {
+                    return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+                }
+            }
+
+            // Reorder directionality run visually.
+            mRunLevels.resize(bidi.getRunCount());
+            byte[] levels = mRunLevels.getRawArray();
+            for (int i = 0; i < bidi.getRunCount(); ++i) {
+                levels[i] = (byte) bidi.getRunLevel(i);
+            }
+            int[] visualOrders = Bidi.reorderVisual(levels);
+
+            int[] dirs = new int[bidi.getRunCount() * 2];
+            for (int i = 0; i < bidi.getRunCount(); ++i) {
+                int vIndex;
+                if ((mBidi.getBaseLevel() & 0x01) == 1) {
+                    // For the historical reasons, if the base directionality is RTL, the Android
+                    // draws from the right, i.e. the visually reordered run needs to be reversed.
+                    vIndex = visualOrders[bidi.getRunCount() - i - 1];
+                } else {
+                    vIndex = visualOrders[i];
+                }
+
+                // Special packing of dire
+                dirs[i * 2] = bidi.getRunStart(vIndex);
+                dirs[i * 2 + 1] = bidi.getRunLevel(vIndex) << Layout.RUN_LEVEL_SHIFT
+                        | (bidi.getRunLimit(vIndex) - dirs[i * 2]);
+            }
+
+            return new Directions(dirs);
+        }
         if (mLtrWithoutBidi) {
             return Layout.DIRS_ALL_LEFT_TO_RIGHT;
         }
@@ -608,6 +680,37 @@
             }
         }
 
+        if (ClientFlags.icuBidiMigration()) {
+            if ((textDir == TextDirectionHeuristics.LTR
+                    || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+                    || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+                    && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+                mLevels.clear();
+                mLtrWithoutBidi = true;
+                return;
+            }
+            final int bidiRequest;
+            if (textDir == TextDirectionHeuristics.LTR) {
+                bidiRequest = Bidi.LTR;
+            } else if (textDir == TextDirectionHeuristics.RTL) {
+                bidiRequest = Bidi.RTL;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+                bidiRequest = Bidi.LEVEL_DEFAULT_LTR;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+                bidiRequest = Bidi.LEVEL_DEFAULT_RTL;
+            } else {
+                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+                bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
+            }
+            mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+            mLevels.resize(mTextLength);
+            byte[] rawArray = mLevels.getRawArray();
+            for (int i = 0; i < mTextLength; ++i) {
+                rawArray[i] = mBidi.getLevelAt(i);
+            }
+            mLtrWithoutBidi = false;
+            return;
+        }
         if ((textDir == TextDirectionHeuristics.LTR
                 || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
                 || textDir == TextDirectionHeuristics.ANYRTL_LTR)
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 2466386..770e5c9 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -59,6 +59,7 @@
             Flags.FLAG_PHRASE_STRICT_FALLBACK,
             Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
             Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
+            Flags.FLAG_ICU_BIDI_MIGRATION,
     };
 
     /**
@@ -71,6 +72,7 @@
             Flags.phraseStrictFallback(),
             Flags.useBoundsForWidth(),
             Flags.fixLineHeightForLocale(),
+            Flags.icuBidiMigration(),
     };
 
     /**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 224e5d8..bde9c77 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -305,7 +305,7 @@
             }
             mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
             mAddedLetterSpacingInPx = 0;
-        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_LETTER_SPACING
             LineInfo lineInfo = new LineInfo();
             float width = Math.abs(measure(end, false, null, null, lineInfo));
 
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f3e0ea7..f37c4c2a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -77,7 +77,7 @@
 }
 
 flag {
-  name: "inter_character_justification"
+  name: "letter_spacing_justification"
   namespace: "text"
   description: "A feature flag that implement inter character justification."
   bug: "283193133"
@@ -103,3 +103,10 @@
   description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
   bug: "300850862"
 }
+
+flag {
+  name: "icu_bidi_migration"
+  namespace: "text"
+  description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
+  bug: "317144801"
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java
index 4994501..3710b4d 100644
--- a/core/java/android/tracing/perfetto/DataSourceInstance.java
+++ b/core/java/android/tracing/perfetto/DataSourceInstance.java
@@ -69,4 +69,8 @@
     public final void release() {
         mDataSource.releaseDataSourceInstance(mInstanceIndex);
     }
+
+    public final int getInstanceIndex() {
+        return mInstanceIndex;
+    }
 }
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
index baece75..744f446 100644
--- a/core/java/android/tracing/transition/TransitionDataSource.java
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -16,7 +16,6 @@
 
 package android.tracing.transition;
 
-import android.tracing.perfetto.CreateTlsStateArgs;
 import android.tracing.perfetto.DataSource;
 import android.tracing.perfetto.DataSourceInstance;
 import android.tracing.perfetto.FlushCallbackArguments;
@@ -24,14 +23,11 @@
 import android.tracing.perfetto.StopCallbackArguments;
 import android.util.proto.ProtoInputStream;
 
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * @hide
  */
 public class TransitionDataSource
-        extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> {
+        extends DataSource<DataSourceInstance, Void, Void> {
     public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition";
 
     private final Runnable mOnStartStaticCallback;
@@ -46,15 +42,6 @@
     }
 
     @Override
-    protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
-        return new TlsState();
-    }
-
-    public class TlsState {
-        public final Map<String, Integer> handlerMapping = new HashMap<>();
-    }
-
-    @Override
     public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
         return new DataSourceInstance(this, instanceIndex) {
             @Override
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index f819c9b..db665a9 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -27,9 +27,7 @@
 import static android.view.DisplayCutoutProto.SIDE_OVERRIDES;
 import static android.view.DisplayCutoutProto.WATERFALL_INSETS;
 import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
@@ -168,6 +166,9 @@
     // The side index is always under the natural rotation of the device.
     private int[] mSideOverrides;
 
+    static final int[] INVALID_OVERRIDES = new int[]{INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE,
+            INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE};
+
     /** @hide */
     @IntDef(prefix = { "BOUNDS_POSITION_" }, value = {
             BOUNDS_POSITION_LEFT,
@@ -1157,35 +1158,25 @@
         final int resourceId = index >= 0 && index < array.length()
                 ? array.getResourceId(index, ID_NULL)
                 : ID_NULL;
-        final String[] rawOverrides = resourceId != ID_NULL
-                ? array.getResources().getStringArray(resourceId)
-                : res.getStringArray(R.array.config_mainBuiltInDisplayCutoutSideOverride);
+        final int[] rawOverrides = resourceId != ID_NULL
+                ? array.getResources().getIntArray(resourceId)
+                : res.getIntArray(R.array.config_mainBuiltInDisplayCutoutSideOverride);
         array.recycle();
-        final int[] override = new int[]{INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE,
-                INVALID_SIDE_OVERRIDE, INVALID_SIDE_OVERRIDE};
-        for (String rawOverride : rawOverrides) {
-            int rotation;
-            String[] split = rawOverride.split(" *, *");
-            switch (split[0]) {
-                case "0" -> rotation = ROTATION_0;
-                case "90" -> rotation = ROTATION_90;
-                case "180" -> rotation = ROTATION_180;
-                case "270" -> rotation = ROTATION_270;
-                default -> throw new IllegalArgumentException("Invalid side override definition: "
-                            + rawOverride);
-            }
-            int side;
-            switch (split[1]) {
-                case SIDE_STRING_LEFT -> side = BOUNDS_POSITION_LEFT;
-                case SIDE_STRING_TOP -> side = BOUNDS_POSITION_TOP;
-                case SIDE_STRING_RIGHT -> side = BOUNDS_POSITION_RIGHT;
-                case SIDE_STRING_BOTTOM -> side = BOUNDS_POSITION_BOTTOM;
-                default -> throw new IllegalArgumentException("Invalid side override definition: "
-                        + rawOverride);
-            }
-            override[rotation] = side;
+        if (rawOverrides.length == 0) {
+            return INVALID_OVERRIDES;
+        } else if (rawOverrides.length != 4) {
+            throw new IllegalArgumentException(
+                    "Invalid side override definition, exact 4 overrides required: "
+                    + Arrays.toString(rawOverrides));
         }
-        return override;
+        for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
+            if (rawOverrides[rotation] < BOUNDS_POSITION_LEFT
+                    || rawOverrides[rotation] >= BOUNDS_POSITION_LENGTH) {
+                throw new IllegalArgumentException("Invalid side override definition: "
+                        + Arrays.toString(rawOverrides));
+            }
+        }
+        return rawOverrides;
     }
 
     /**
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 064bc69..35b137a 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -314,13 +314,20 @@
         if (count < 2) {
             return null;
         }
+        View next = null;
+        final boolean[] looped = new boolean[1];
         switch (direction) {
             case View.FOCUS_FORWARD:
-                return getNextFocusable(focused, focusables, count);
+                next = getNextFocusable(focused, focusables, count, looped);
+                break;
             case View.FOCUS_BACKWARD:
-                return getPreviousFocusable(focused, focusables, count);
+                next = getPreviousFocusable(focused, focusables, count, looped);
+                break;
         }
-        return focusables.get(count - 1);
+        if (root != null && root.mAttachInfo != null && root == root.getRootView()) {
+            root.mAttachInfo.mNextFocusLooped = looped[0];
+        }
+        return next != null ? next : focusables.get(count - 1);
     }
 
     private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
@@ -375,7 +382,8 @@
         return closest;
     }
 
-    private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+    private static View getNextFocusable(View focused, ArrayList<View> focusables, int count,
+            boolean[] outLooped) {
         if (count < 2) {
             return null;
         }
@@ -385,10 +393,12 @@
                 return focusables.get(position + 1);
             }
         }
+        outLooped[0] = true;
         return focusables.get(0);
     }
 
-    private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+    private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count,
+            boolean[] outLooped) {
         if (count < 2) {
             return null;
         }
@@ -398,6 +408,7 @@
                 return focusables.get(position - 1);
             }
         }
+        outLooped[0] = true;
         return focusables.get(count - 1);
     }
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7903050..99863d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1085,7 +1085,9 @@
 
     void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
 
+    @EnforcePermission("DETECT_SCREEN_RECORDING")
     boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
 
+    @EnforcePermission("DETECT_SCREEN_RECORDING")
     void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
 }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f2c3abc..d22d2a5 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,7 +16,10 @@
 
 package android.view;
 
+import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +31,7 @@
 import android.hardware.SensorManager;
 import android.hardware.input.HostUsiVersion;
 import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
 import android.hardware.lights.LightsManager;
 import android.icu.util.ULocale;
@@ -90,6 +94,8 @@
     private final int mAssociatedDisplayId;
     private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
 
+    private final ViewBehavior mViewBehavior = new ViewBehavior(this);
+
     @GuardedBy("mMotionRanges")
     private Vibrator mVibrator; // guarded by mMotionRanges during initialization
 
@@ -539,6 +545,8 @@
             addMotionRange(in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
                     in.readFloat(), in.readFloat(), in.readFloat());
         }
+
+        mViewBehavior.mShouldSmoothScroll = in.readBoolean();
     }
 
     /**
@@ -571,6 +579,7 @@
         private int mUsiVersionMinor = -1;
         private int mAssociatedDisplayId = Display.INVALID_DISPLAY;
         private List<MotionRange> mMotionRanges = new ArrayList<>();
+        private boolean mShouldSmoothScroll;
 
         /** @see InputDevice#getId() */
         public Builder setId(int id) {
@@ -706,6 +715,16 @@
             return this;
         }
 
+        /**
+         * Sets the view behavior for smooth scrolling ({@code false} by default).
+         *
+         * @see ViewBehavior#shouldSmoothScroll(int, int)
+         */
+        public Builder setShouldSmoothScroll(boolean shouldSmoothScroll) {
+            mShouldSmoothScroll = shouldSmoothScroll;
+            return this;
+        }
+
         /** Build {@link InputDevice}. */
         public InputDevice build() {
             InputDevice device = new InputDevice(
@@ -745,6 +764,8 @@
                         range.getResolution());
             }
 
+            device.setShouldSmoothScroll(mShouldSmoothScroll);
+
             return device;
         }
     }
@@ -1123,6 +1144,22 @@
         return mMotionRanges;
     }
 
+    /**
+     * Provides the {@link ViewBehavior} for the device.
+     *
+     * <p>This behavior is designed to be obtained using the
+     * {@link InputManager#getInputDeviceViewBehavior(int)} API, to allow associating the behavior
+     * with a {@link Context} (since input device is not associated with a context).
+     * The ability to associate the behavior with a context opens capabilities like linking the
+     * behavior to user settings, for example.
+     *
+     * @hide
+     */
+    @NonNull
+    public ViewBehavior getViewBehavior() {
+        return mViewBehavior;
+    }
+
     // Called from native code.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void addMotionRange(int axis, int source,
@@ -1130,6 +1167,11 @@
         mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz, resolution));
     }
 
+    // Called from native code.
+    private void setShouldSmoothScroll(boolean shouldSmoothScroll) {
+        mViewBehavior.mShouldSmoothScroll = shouldSmoothScroll;
+    }
+
     /**
      * Returns the Bluetooth address of this input device, if known.
      *
@@ -1447,6 +1489,82 @@
         }
     }
 
+    /**
+     * Provides information on how views processing {@link MotionEvent}s generated by this input
+     * device should respond to the events. Use {@link InputManager#getInputDeviceViewBehavior(int)}
+     * to get an instance of the view behavior for an input device.
+     *
+     * <p>See an example below how a {@link View} can use this class to determine and apply the
+     * scrolling behavior for a generic {@link MotionEvent}.
+     *
+     * <pre>{@code
+     *     public boolean onGenericMotionEvent(MotionEvent event) {
+     *         InputManager manager = context.getSystemService(InputManager.class);
+     *         ViewBehavior viewBehavior = manager.getInputDeviceViewBehavior(event.getDeviceId());
+     *         // Assume a helper function that tells us which axis to use for scrolling purpose.
+     *         int axis = getScrollAxisForGenericMotionEvent(event);
+     *         int source = event.getSource();
+     *
+     *         boolean shouldSmoothScroll =
+     *                 viewBehavior != null && viewBehavior.shouldSmoothScroll(axis, source);
+     *         // Proceed to running the scrolling logic...
+     *     }
+     * }</pre>
+     *
+     * @see InputManager#getInputDeviceViewBehavior(int)
+     */
+    @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+    public static final class ViewBehavior {
+        private static final boolean DEFAULT_SHOULD_SMOOTH_SCROLL = false;
+
+        private final InputDevice mInputDevice;
+
+        // TODO(b/246946631): implement support for InputDevices to adjust this configuration
+        // by axis and source. When implemented, the axis/source specific config will take
+        // precedence over this global config.
+        /** A global smooth scroll configuration applying to all motion axis and input source. */
+        private boolean mShouldSmoothScroll = DEFAULT_SHOULD_SMOOTH_SCROLL;
+
+        /** @hide */
+        public ViewBehavior(@NonNull InputDevice inputDevice) {
+            mInputDevice = inputDevice;
+        }
+
+        /**
+         * Returns whether a view should smooth scroll when scrolling due to a {@link MotionEvent}
+         * generated by the input device.
+         *
+         * <p>Smooth scroll in this case refers to a scroll that animates the transition between
+         * the starting and ending positions of the scroll. When this method returns {@code true},
+         * views should try to animate a scroll generated by this device at the given axis and with
+         * the given source to produce a good scroll user experience. If this method returns
+         * {@code false}, animating scrolls is not necessary.
+         *
+         * <p>If the input device does not have a {@link MotionRange} with the provided axis and
+         * source, this method returns {@code false}.
+         *
+         * @param axis the {@link MotionEvent} axis whose value is used to get the scroll extent.
+         * @param source the {@link InputDevice} source from which the {@link MotionEvent} that
+         *      triggers the scroll came.
+         * @return {@code true} if smooth scrolling should be used for the scroll, or {@code false}
+         *      if smooth scrolling is not necessary, or if the provided axis and source combination
+         *      is not available for the input device.
+         */
+        @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+        public boolean shouldSmoothScroll(int axis, int source) {
+            // Note: although we currently do not use axis and source in computing the return value,
+            // we will keep the API params to avoid further public API changes when we start
+            // supporting axis/source configuration. Also, having these params lets OEMs provide
+            // their custom implementation of the API that depends on axis and source.
+
+            // TODO(b/246946631): speed up computation using caching of results.
+            if (mInputDevice.getMotionRange(axis, source) == null) {
+                return false;
+            }
+            return mShouldSmoothScroll;
+        }
+    }
+
     @Override
     public void writeToParcel(Parcel out, int flags) {
         mKeyCharacterMap.writeToParcel(out, flags);
@@ -1484,6 +1602,8 @@
             out.writeFloat(range.mFuzz);
             out.writeFloat(range.mResolution);
         }
+
+        out.writeBoolean(mViewBehavior.mShouldSmoothScroll);
     }
 
     @Override
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 59ec605..9db1060 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -162,6 +162,12 @@
     public float alpha;
 
     /**
+     * Sets a property on this window indicating that its visible region should be considered when
+     * computing TrustedPresentation Thresholds.
+     */
+    public boolean canOccludePresentation;
+
+    /**
      * The input token for the window to which focus should be transferred when this input window
      * can be successfully focused. If null, this input window will not transfer its focus to
      * any other window.
@@ -205,6 +211,7 @@
         focusTransferTarget = other.focusTransferTarget;
         contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight());
         alpha = other.alpha;
+        canOccludePresentation = other.canOccludePresentation;
     }
 
     @Override
@@ -219,6 +226,7 @@
                 .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
                 .append(", contentSize=").append(contentSize)
                 .append(", alpha=").append(alpha)
+                .append(", canOccludePresentation=").append(canOccludePresentation)
                 .toString();
 
     }
diff --git a/core/java/android/view/ScreenRecordingCallbacks.java b/core/java/android/view/ScreenRecordingCallbacks.java
new file mode 100644
index 0000000..ee55737
--- /dev/null
+++ b/core/java/android/view/ScreenRecordingCallbacks.java
@@ -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 android.view;
+
+import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.WindowManager.ScreenRecordingState;
+import android.window.IScreenRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for calling app-registered screen recording callbacks. This class
+ * registers a single screen recording callback with WindowManagerService and calls the
+ * app-registered callbacks whenever that WindowManagerService callback is called.
+ *
+ * @hide
+ */
+public final class ScreenRecordingCallbacks {
+
+    private static ScreenRecordingCallbacks sInstance;
+    private static final Object sLock = new Object();
+
+    private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
+            new ArrayMap<>();
+
+    private IScreenRecordingCallback mCallbackNotifier;
+    private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;
+
+    private ScreenRecordingCallbacks() {}
+
+    private static @NonNull IWindowManager getWindowManagerService() {
+        return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
+    }
+
+    static ScreenRecordingCallbacks getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new ScreenRecordingCallbacks();
+            }
+            return sInstance;
+        }
+    }
+
+    @RequiresPermission(DETECT_SCREEN_RECORDING)
+    @ScreenRecordingState
+    int addCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        synchronized (sLock) {
+            if (mCallbackNotifier == null) {
+                mCallbackNotifier =
+                        new IScreenRecordingCallback.Stub() {
+                            @Override
+                            public void onScreenRecordingStateChanged(
+                                    boolean visibleInScreenRecording) {
+                                int state =
+                                        visibleInScreenRecording
+                                                ? SCREEN_RECORDING_STATE_VISIBLE
+                                                : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+                                notifyCallbacks(state);
+                            }
+                        };
+                try {
+                    boolean visibleInScreenRecording =
+                            getWindowManagerService()
+                                    .registerScreenRecordingCallback(mCallbackNotifier);
+                    mState =
+                            visibleInScreenRecording
+                                    ? SCREEN_RECORDING_STATE_VISIBLE
+                                    : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+            mCallbacks.put(callback, executor);
+            return mState;
+        }
+    }
+
+    @RequiresPermission(DETECT_SCREEN_RECORDING)
+    void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        synchronized (sLock) {
+            mCallbacks.remove(callback);
+            if (mCallbacks.isEmpty()) {
+                try {
+                    getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+                mCallbackNotifier = null;
+            }
+        }
+    }
+
+    private void notifyCallbacks(@ScreenRecordingState int state) {
+        List<Runnable> callbacks;
+        synchronized (sLock) {
+            mState = state;
+            if (mCallbacks.isEmpty()) {
+                return;
+            }
+
+            callbacks = new ArrayList<>();
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                Consumer<Integer> callback = mCallbacks.keyAt(i);
+                Executor executor = mCallbacks.valueAt(i);
+                callbacks.add(() -> executor.execute(() -> callback.accept(state)));
+            }
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (int i = 0; i < callbacks.size(); i++) {
+                callbacks.get(i).run();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3ed0385..3c0ac06 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -168,6 +168,8 @@
             boolean isTrustedOverlay);
     private static native void nativeSetDropInputMode(
             long transactionObj, long nativeObject, int flags);
+    private static native void nativeSetCanOccludePresentation(long transactionObj,
+            long nativeObject, boolean canOccludePresentation);
     private static native void nativeSurfaceFlushJankData(long nativeSurfaceObject);
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -589,6 +591,28 @@
     public static final int DISPLAY_DECORATION = 0x00000200;
 
     /**
+     * Ignore any destination frame set on the layer. This is used when the buffer scaling mode
+     * is freeze and the destination frame is applied asynchronously with the buffer submission.
+     * This is needed to maintain compatibility for SurfaceView scaling behavior.
+     * See SurfaceView scaling behavior for more details.
+     * @hide
+     */
+    public static final int IGNORE_DESTINATION_FRAME = 0x00000400;
+
+    /**
+     * Special casing for layer that is a refresh rate indicator
+     * @hide
+     */
+    public static final int LAYER_IS_REFRESH_RATE_INDICATOR = 0x00000800;
+
+    /**
+     * Sets a property on this layer indicating that its visible region should be considered when
+     * computing TrustedPresentation Thresholds
+     * @hide
+     */
+    public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
+
+    /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
      * meaningless for surfaces without an alpha channel. By default
@@ -4163,6 +4187,29 @@
         }
 
         /**
+         * Sets a property on this SurfaceControl and all its children indicating that the visible
+         * region of this SurfaceControl should be considered when computing TrustedPresentation
+         * Thresholds.
+         * <p>
+         * API Guidance:
+         * The goal of this API is to identify windows that can be used to occlude content on
+         * another window. This includes windows controlled by the user or the system. If the window
+         * is transient, like Toast or notification shade, the window should not set this flag since
+         * the user or the app cannot use the window to occlude content in a persistent manner. All
+         * apps should have this flag set.
+         * <p>
+         * The caller must hold the ACCESS_SURFACE_FLINGER permission.
+         * @hide
+         */
+        public Transaction setCanOccludePresentation(SurfaceControl sc,
+                boolean canOccludePresentation) {
+            checkPreconditions(sc);
+            final int value = (canOccludePresentation) ? CAN_OCCLUDE_PRESENTATION : 0;
+            nativeSetFlags(mNativeObject, sc.mNativeObject, value, CAN_OCCLUDE_PRESENTATION);
+            return this;
+        }
+
+        /**
          * Sends a flush jank data transaction for the given surface.
          * @hide
          */
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 163dfa2..021bbf7 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -197,7 +196,6 @@
     private Canvas mCanvas;
     private int mSaveCount;
 
-    @FloatRange(from = 0.0) float mFrameRate;
     @Surface.FrameRateCompatibility int mFrameRateCompatibility;
 
     private final Object[] mNativeWindowLock = new Object[0];
@@ -473,13 +471,13 @@
             mLayer.setSurfaceTexture(mSurface);
             mSurface.setDefaultBufferSize(getWidth(), getHeight());
             mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
-            if (Flags.toolkitSetFrameRate()) {
+            if (Flags.toolkitSetFrameRateReadOnly()) {
                 mSurface.setOnSetFrameRateListener(
                         (surfaceTexture, frameRate, compatibility, strategy) -> {
                             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                                 Trace.instant(Trace.TRACE_TAG_VIEW, "setFrameRate: " + frameRate);
                             }
-                            mFrameRate = frameRate;
+                            setRequestedFrameRate(frameRate);
                             mFrameRateCompatibility = compatibility;
                         }, mAttachInfo.mHandler);
             }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1b22fda..5c5817f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5537,10 +5537,20 @@
      */
     private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
 
+
+    private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
+    private static final int INFREQUENT_UPDATE_COUNTS = 2;
+
     // The preferred frame rate of the view that is mainly used for
     // touch boosting, view velocity handling, and TextureView.
     private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
 
+    private int mInfrequentUpdateCount = 0;
+    private long mLastUpdateTimeMillis = 0;
+    private long mMinusOneFrameIntervalMillis = 0;
+    private long mMinusTwoFrameIntervalMillis = 0;
+    private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0;
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -15858,20 +15868,7 @@
         }
 
         if (onFilterTouchEventForSecurity(event)) {
-            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
-                result = true;
-            }
-            //noinspection SimplifiableIfStatement
-            ListenerInfo li = mListenerInfo;
-            if (li != null && li.mOnTouchListener != null
-                    && (mViewFlags & ENABLED_MASK) == ENABLED
-                    && li.mOnTouchListener.onTouch(this, event)) {
-                result = true;
-            }
-
-            if (!result && onTouchEvent(event)) {
-                result = true;
-            }
+            result = performOnTouchCallback(event);
         }
 
         if (!result && mInputEventConsistencyVerifier != null) {
@@ -15890,6 +15887,36 @@
         return result;
     }
 
+    /**
+     * Returns {@code true} if the {@link MotionEvent} from {@link #dispatchTouchEvent} was
+     * handled by this view.
+     */
+    private boolean performOnTouchCallback(MotionEvent event) {
+        boolean handled = false;
+        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
+            handled = true;
+        }
+        //noinspection SimplifiableIfStatement
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View.onTouchListener#onTouch");
+                handled = li.mOnTouchListener.onTouch(this, event);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+        }
+        if (handled) {
+            return true;
+        }
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View#onTouchEvent");
+            return onTouchEvent(event);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
     boolean isAccessibilityFocusedViewOrHost() {
         return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
                 .getAccessibilityFocusedHost() == this);
@@ -20253,7 +20280,10 @@
         }
 
         // For VRR to vote the preferred frame rate
-        votePreferredFrameRate();
+        if (sToolkitSetFrameRateReadOnlyFlagValue) {
+            updateInfrequentCount();
+            votePreferredFrameRate();
+        }
 
         // Reset content capture caches
         mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
@@ -20358,7 +20388,10 @@
     protected void damageInParent() {
         if (mParent != null && mAttachInfo != null) {
             // For VRR to vote the preferred frame rate
-            votePreferredFrameRate();
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                updateInfrequentCount();
+                votePreferredFrameRate();
+            }
             mParent.onDescendantInvalidated(this, this);
         }
     }
@@ -31336,6 +31369,13 @@
         final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
 
         /**
+         * Indicates if the next focus will be looped back to the first focusable view of the entire
+         * hierarchy when finding in the direction of {@link #FOCUS_FORWARD} or to the last
+         * focusable view when finding in the direction of {@link #FOCUS_BACKWARD}.
+         */
+        boolean mNextFocusLooped = false;
+
+        /**
          * The id of the window for accessibility purposes.
          */
         int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -33124,11 +33164,20 @@
     }
 
     private int calculateFrameRateCategory(float sizePercentage) {
-        if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
-            return FRAME_RATE_CATEGORY_LOW;
-        } else {
+        if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+                < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+            if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+                return FRAME_RATE_CATEGORY_NORMAL;
+            } else {
+                return FRAME_RATE_CATEGORY_HIGH;
+            }
+        }
+
+        if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
             return FRAME_RATE_CATEGORY_NORMAL;
         }
+
+        return mLastFrameRateCategory;
     }
 
     private void votePreferredFrameRate() {
@@ -33137,22 +33186,22 @@
         float sizePercentage = getSizePercentage();
         int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
         if (viewRootImpl != null && sizePercentage > 0) {
-            if (sToolkitSetFrameRateReadOnlyFlagValue) {
-                if (mPreferredFrameRate < 0) {
-                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
-                    }
-                } else {
-                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+            if (mPreferredFrameRate < 0) {
+                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
                 }
-                viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+            } else {
+                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
             }
+            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+            mLastFrameRateCategory = frameRateCateogry;
+
             if (sToolkitMetricsForFrameRateDecisionFlagValue) {
                 viewRootImpl.recordViewPercentage(sizePercentage);
             }
@@ -33231,4 +33280,27 @@
         }
         return 0;
     }
+
+    /**
+     * This function is mainly used for migrating infrequent layer lagic
+     * from SurfaceFlinger to Toolkit.
+     * The infrequent layter logic includes:
+     * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+     * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+     * - otherwise, use the previous category value.
+     */
+    private void updateInfrequentCount() {
+        long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+        long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
+        mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
+        mMinusOneFrameIntervalMillis = timeIntervalMillis;
+
+        mLastUpdateTimeMillis = currentTimeMillis;
+        if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+            mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+                        ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
+        } else {
+            mInfrequentUpdateCount = 0;
+        }
+    }
 }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 25e0eca..4f1fb40 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -7,7 +7,7 @@
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
+ * Unless required by applicable law or agreed to in writing, softwareViewDebug
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -34,10 +36,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import libcore.util.HexEncoding;
 
 import java.io.BufferedOutputStream;
@@ -54,9 +59,11 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -67,7 +74,6 @@
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.stream.Stream;
@@ -76,6 +82,9 @@
  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
  */
 public class ViewDebug {
+
+    private static final String TAG = "ViewDebug";
+
     /**
      * @deprecated This flag is now unused
      */
@@ -425,6 +434,7 @@
     private static final String REMOTE_PROFILE = "PROFILE";
     private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
     private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
+    private static final String REMOTE_COMMAND_INVOKE_METHOD = "INVOKE_METHOD";
 
     private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
     private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
@@ -555,6 +565,8 @@
                 requestLayout(view, params[0]);
             } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
                 profile(view, clientStream, params[0]);
+            } else if (REMOTE_COMMAND_INVOKE_METHOD.equals(command)) {
+                invokeViewMethod(view, clientStream, params);
             }
         }
     }
@@ -1825,46 +1837,84 @@
         Log.d(tag, sb.toString());
     }
 
+    private static void invokeViewMethod(View root, OutputStream clientStream, String[] params)
+            throws IOException {
+        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+        try {
+            if (params.length < 2) {
+                throw new IllegalArgumentException("Missing parameter");
+            }
+            View targetView = findView(root, params[0]);
+            if (targetView == null) {
+                throw new IllegalArgumentException("View not found: " + params[0]);
+            }
+            String method = params[1];
+            ByteBuffer args = ByteBuffer.wrap(params.length < 2
+                    ? new byte[0]
+                    : Base64.decode(params[2], Base64.NO_WRAP));
+            byte[] result = invokeViewMethod(targetView, method, args);
+            out.write("1");
+            out.newLine();
+            out.write(Base64.encodeToString(result, Base64.NO_WRAP));
+            out.newLine();
+        } catch (Exception e) {
+            out.write("-1");
+            out.newLine();
+            out.write(e.getMessage());
+            out.newLine();
+        } finally {
+            out.close();
+        }
+    }
+
     /**
      * Invoke a particular method on given view.
      * The given method is always invoked on the UI thread. The caller thread will stall until the
      * method invocation is complete. Returns an object equal to the result of the method
      * invocation, null if the method is declared to return void
+     * @param params all the method parameters encoded in a byteArray
      * @throws Exception if the method invocation caused any exception
      * @hide
      */
-    public static Object invokeViewMethod(final View view, final Method method,
-            final Object[] args) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<Object> result = new AtomicReference<Object>();
-        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+    public static byte[] invokeViewMethod(View targetView, String methodName, ByteBuffer params)
+            throws ViewMethodInvocationSerializationException {
+        Class<?>[] argTypes;
+        Object[] args;
+        if (!params.hasRemaining()) {
+            argTypes = new Class<?>[0];
+            args = new Object[0];
+        } else {
+            int nArgs = params.getInt();
+            argTypes = new Class<?>[nArgs];
+            args = new Object[nArgs];
 
-        view.post(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    result.set(method.invoke(view, args));
-                } catch (InvocationTargetException e) {
-                    exception.set(e.getCause());
-                } catch (Exception e) {
-                    exception.set(e);
-                }
+            deserializeMethodParameters(args, argTypes, params);
+        }
 
-                latch.countDown();
-            }
-        });
+        Method method;
+        try {
+            method = targetView.getClass().getMethod(methodName, argTypes);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "No such method: " + e.getMessage());
+            throw new ViewMethodInvocationSerializationException(
+                    "No such method: " + e.getMessage());
+        }
 
         try {
-            latch.await();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
+            // Invoke the method on Views handler
+            FutureTask<Object> task = new FutureTask<>(() -> method.invoke(targetView, args));
+            targetView.post(task);
+            Object result = task.get();
+            Class<?> returnType = method.getReturnType();
+            return serializeReturnValue(returnType, returnType.cast(result));
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
+            String msg = e.getCause().getMessage();
+            if (msg == null) {
+                msg = e.getCause().toString();
+            }
+            throw new RuntimeException(msg);
         }
-
-        if (exception.get() != null) {
-            throw new RuntimeException(exception.get());
-        }
-
-        return result.get();
     }
 
     /**
@@ -1961,4 +2011,175 @@
          */
         Bitmap createBitmap();
     }
+
+    /**
+     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
+     * buffer.
+     *
+     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
+     * be the same length, and will be set to the argument types of the data read.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static void deserializeMethodParameters(
+            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
+            ViewMethodInvocationSerializationException {
+        checkArgument(args.length == argTypes.length);
+
+        for (int i = 0; i < args.length; i++) {
+            char typeSignature = in.getChar();
+            boolean isArray = typeSignature == SIG_ARRAY;
+            if (isArray) {
+                char arrayType = in.getChar();
+                if (arrayType != SIG_BYTE) {
+                    // This implementation only supports byte-arrays for now.
+                    throw new ViewMethodInvocationSerializationException(
+                            "Unsupported array parameter type (" + typeSignature
+                                    + ") to invoke view method @argument " + i);
+                }
+
+                int arrayLength = in.getInt();
+                if (arrayLength > in.remaining()) {
+                    // The sender did not actually sent the specified amount of bytes. This
+                    // avoids a malformed packet to trigger an out-of-memory error.
+                    throw new BufferUnderflowException();
+                }
+
+                byte[] byteArray = new byte[arrayLength];
+                in.get(byteArray);
+
+                argTypes[i] = byte[].class;
+                args[i] = byteArray;
+            } else {
+                switch (typeSignature) {
+                    case SIG_BOOLEAN:
+                        argTypes[i] = boolean.class;
+                        args[i] = in.get() != 0;
+                        break;
+                    case SIG_BYTE:
+                        argTypes[i] = byte.class;
+                        args[i] = in.get();
+                        break;
+                    case SIG_CHAR:
+                        argTypes[i] = char.class;
+                        args[i] = in.getChar();
+                        break;
+                    case SIG_SHORT:
+                        argTypes[i] = short.class;
+                        args[i] = in.getShort();
+                        break;
+                    case SIG_INT:
+                        argTypes[i] = int.class;
+                        args[i] = in.getInt();
+                        break;
+                    case SIG_LONG:
+                        argTypes[i] = long.class;
+                        args[i] = in.getLong();
+                        break;
+                    case SIG_FLOAT:
+                        argTypes[i] = float.class;
+                        args[i] = in.getFloat();
+                        break;
+                    case SIG_DOUBLE:
+                        argTypes[i] = double.class;
+                        args[i] = in.getDouble();
+                        break;
+                    case SIG_STRING: {
+                        argTypes[i] = String.class;
+                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
+                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
+                        in.get(rawStringBuffer);
+                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
+                        break;
+                    }
+                    default:
+                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
+                        throw new ViewMethodInvocationSerializationException(
+                                "Unsupported parameter type (" + typeSignature
+                                        + ") to invoke view method.");
+                }
+            }
+
+        }
+    }
+
+    /**
+     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
+     * @hide
+     */
+    @VisibleForTesting
+    public static byte[] serializeReturnValue(Class<?> type, Object value)
+            throws ViewMethodInvocationSerializationException, IOException {
+        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
+        DataOutputStream dos = new DataOutputStream(byteOutStream);
+
+        if (type.isArray()) {
+            if (!type.equals(byte[].class)) {
+                // Only byte arrays are supported currently.
+                throw new ViewMethodInvocationSerializationException(
+                        "Unsupported array return type (" + type + ")");
+            }
+            byte[] byteArray = (byte[]) value;
+            dos.writeChar(SIG_ARRAY);
+            dos.writeChar(SIG_BYTE);
+            dos.writeInt(byteArray.length);
+            dos.write(byteArray);
+        } else if (boolean.class.equals(type)) {
+            dos.writeChar(SIG_BOOLEAN);
+            dos.write((boolean) value ? 1 : 0);
+        } else if (byte.class.equals(type)) {
+            dos.writeChar(SIG_BYTE);
+            dos.writeByte((byte) value);
+        } else if (char.class.equals(type)) {
+            dos.writeChar(SIG_CHAR);
+            dos.writeChar((char) value);
+        } else if (short.class.equals(type)) {
+            dos.writeChar(SIG_SHORT);
+            dos.writeShort((short) value);
+        } else if (int.class.equals(type)) {
+            dos.writeChar(SIG_INT);
+            dos.writeInt((int) value);
+        } else if (long.class.equals(type)) {
+            dos.writeChar(SIG_LONG);
+            dos.writeLong((long) value);
+        } else if (double.class.equals(type)) {
+            dos.writeChar(SIG_DOUBLE);
+            dos.writeDouble((double) value);
+        } else if (float.class.equals(type)) {
+            dos.writeChar(SIG_FLOAT);
+            dos.writeFloat((float) value);
+        } else if (String.class.equals(type)) {
+            dos.writeChar(SIG_STRING);
+            dos.writeUTF(value != null ? (String) value : "");
+        } else {
+            dos.writeChar(SIG_VOID);
+        }
+
+        return byteOutStream.toByteArray();
+    }
+
+    // Prefixes for simple primitives. These match the JNI definitions.
+    private static final char SIG_ARRAY = '[';
+    private static final char SIG_BOOLEAN = 'Z';
+    private static final char SIG_BYTE = 'B';
+    private static final char SIG_SHORT = 'S';
+    private static final char SIG_CHAR = 'C';
+    private static final char SIG_INT = 'I';
+    private static final char SIG_LONG = 'J';
+    private static final char SIG_FLOAT = 'F';
+    private static final char SIG_DOUBLE = 'D';
+    private static final char SIG_VOID = 'V';
+    // Prefixes for some commonly used objects
+    private static final char SIG_STRING = 'R';
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static class ViewMethodInvocationSerializationException extends Exception {
+        ViewMethodInvocationSerializationException(String message) {
+            super(message);
+        }
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c18aeee..e03f857 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -1013,8 +1014,10 @@
     // Used to check if there were any view invalidations in
     // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
     private boolean mHasInvalidation = false;
-    // Used to check if it is in the touch boosting period.
+    // Used to check if it is in the frame rate boosting period.
     private boolean mIsFrameRateBoosting = false;
+    // Used to check if it is in touch boosting period.
+    private boolean mIsTouchBoosting = false;
     // Used to check if there is a message in the message queue
     // for idleness handling.
     private boolean mHasIdledMessage = false;
@@ -1024,6 +1027,16 @@
     private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
     // time for revaluating the idle status before lowering the frame rate.
     private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+    // time for evaluating the interval between current time and
+    // the time when frame rate was set previously.
+    private static final int FRAME_RATE_SETTING_REEVALUATE_TIME = 100;
+
+    /*
+     * the variables below are used to determine whther a dVRR feature should be enabled
+     */
+
+    // Used to determine whether to suppress boost on typing
+    private boolean mShouldSuppressBoostOnTyping = false;
 
     /**
      * A temporary object used so relayoutWindow can return the latest SyncSeqId
@@ -4073,7 +4086,6 @@
         setPreferredFrameRate(mPreferredFrameRate);
         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-        mPreferredFrameRate = 0;
     }
 
     private void createSyncIfNeeded() {
@@ -6127,6 +6139,7 @@
     private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
     private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
     private static final int MSG_REFRESH_POINTER_ICON = 41;
+    private static final int MSG_FRAME_RATE_SETTING = 42;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -6438,11 +6451,12 @@
                      * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
                      */
                     mIsFrameRateBoosting = false;
+                    mIsTouchBoosting = false;
                     setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
                             mLastPreferredFrameRateCategory));
                     break;
                 case MSG_CHECK_INVALIDATION_IDLE:
-                    if (!mHasInvalidation && !mIsFrameRateBoosting) {
+                    if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
                         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                         mHasIdledMessage = false;
@@ -6465,6 +6479,10 @@
                     }
                     updatePointerIcon(mPointerIconEvent);
                     break;
+                case MSG_FRAME_RATE_SETTING:
+                    mPreferredFrameRate = 0;
+                    setPreferredFrameRate(mPreferredFrameRate);
+                    break;
             }
         }
     }
@@ -7283,8 +7301,18 @@
             if (direction != 0) {
                 View focused = mView.findFocus();
                 if (focused != null) {
+                    mAttachInfo.mNextFocusLooped = false;
                     View v = focused.focusSearch(direction);
                     if (v != null && v != focused) {
+                        if (mAttachInfo.mNextFocusLooped) {
+                            // The next focus is looped. Let's try to move the focus to the adjacent
+                            // window. Note: we still need to move the focus in this window
+                            // regardless of what moveFocusToAdjacentWindow returns, so the focus
+                            // can be looped back from the focus in the adjacent window to next
+                            // focus of this window.
+                            moveFocusToAdjacentWindow(direction);
+                        }
+
                         // do the math the get the interesting rect
                         // of previous focused into the coord system of
                         // newly focused view
@@ -7465,7 +7493,7 @@
             // For the variable refresh rate project
             if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
                 // set the frame rate to the maximum value.
-                mIsFrameRateBoosting = true;
+                mIsTouchBoosting = true;
                 setPreferredFrameRateCategory(mPreferredFrameRateCategory);
             }
             /**
@@ -7473,7 +7501,7 @@
              * MotionEvent.ACTION_CANCEL is detected.
              * Not using ACTION_MOVE to avoid checking and sending messages too frequently.
              */
-            if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
+            if (mIsTouchBoosting && (action == MotionEvent.ACTION_UP
                     || action == MotionEvent.ACTION_CANCEL)) {
                 mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
                 mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT,
@@ -11522,15 +11550,6 @@
                 event.setContentChangeTypes(mChangeTypes);
                 if (mAction.isPresent()) event.setAction(mAction.getAsInt());
                 if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
-
-                if (fixMergedContentChangeEvent()) {
-                    if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
-                        final View importantParent = source.getSelfOrParentImportantForA11y();
-                        if (importantParent != null) {
-                            source = importantParent;
-                        }
-                    }
-                }
                 source.sendAccessibilityEventUnchecked(event);
             } else {
                 mLastEventTimeMillis = 0;
@@ -11567,6 +11586,9 @@
             if (mSource != null) {
                 if (fixMergedContentChangeEvent()) {
                     View newSource = getCommonPredecessor(mSource, source);
+                    if (newSource != null) {
+                        newSource = newSource.getSelfOrParentImportantForA11y();
+                    }
                     if (newSource == null) {
                         // If there is no common predecessor, then mSource points to
                         // a removed view, hence in this case always prefer the source.
@@ -12217,17 +12239,32 @@
             return;
         }
 
-        int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
-                ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+        int frameRateCategory = mIsTouchBoosting
+                ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+
+        // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
+        // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
+        // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
+        // (e.g., Window Initialization).
+        if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+            frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+        }
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceBegin(
+                            Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
+                                + frameRateCategory);
+                }
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
                         frameRateCategory, false).applyAsyncUnsafe();
                 mLastPreferredFrameRateCategory = frameRateCategory;
             }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate category", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
 
         if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
@@ -12246,12 +12283,19 @@
 
         try {
             if (mLastPreferredFrameRate != preferredFrameRate) {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceBegin(
+                            Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate "
+                                + preferredFrameRate);
+                }
                 mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
                     Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).applyAsyncUnsafe();
                 mLastPreferredFrameRate = preferredFrameRate;
             }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
     }
 
@@ -12268,14 +12312,14 @@
 
     private boolean shouldSetFrameRate() {
         // use toolkitSetFrameRate flag to gate the change
-        return mPreferredFrameRate > 0 && sToolkitSetFrameRateReadOnlyFlagValue;
+        return sToolkitSetFrameRateReadOnlyFlagValue;
     }
 
     private boolean shouldTouchBoost(int motionEventAction, int windowType) {
         boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
                 || motionEventAction == MotionEvent.ACTION_MOVE
                 || motionEventAction == MotionEvent.ACTION_UP;
-        boolean undesiredType = windowType == TYPE_INPUT_METHOD;
+        boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
         // use toolkitSetFrameRate flag to gate the change
         return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
                 && getFrameRateBoostOnTouchEnabled();
@@ -12319,6 +12363,9 @@
         }
 
         mHasInvalidation = true;
+        mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+        mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+                FRAME_RATE_SETTING_REEVALUATE_TIME);
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c788261..ac2a66e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -128,8 +128,10 @@
 
 import com.android.window.flags.Flags;
 
+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.Arrays;
 import java.util.List;
@@ -1470,6 +1472,34 @@
             "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
 
     /**
+     * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * that declares whether this (embedded) activity allows the system to share its state with the
+     * host app when it is embedded in a different process in
+     * {@link android.R.attr#allowUntrustedActivityEmbedding untrusted mode}.
+     *
+     * <p>If this property is "true", the host app may receive event callbacks for the activity
+     * state change, including the reparent event and the component name of the activity, which are
+     * required to restore the embedding state after the embedded activity exits picture-in-picture
+     * mode. This property does not share any of the activity content with the host. Note that, for
+     * {@link android.R.attr#knownActivityEmbeddingCerts trusted embedding}, the reparent event and
+     * the component name are always shared with the host regardless of the value of this property.
+     *
+     * <p>The default value is {@code false}.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;activity&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"
+     *     android:value="true|false"/&gt;
+     * &lt;/activity&gt;
+     * </pre>
+     */
+    @FlaggedApi(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING)
+    String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING =
+            "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
+
+    /**
      * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
      * that an app can specify to inform the system that the app is activity embedding split feature
      * enabled.
@@ -6117,4 +6147,65 @@
         throw new UnsupportedOperationException(
                 "getDefaultToken is not implemented");
     }
+
+    /** @hide */
+    @Target(ElementType.TYPE_USE)
+    @IntDef(
+            prefix = {"SCREEN_RECORDING_STATE"},
+            value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ScreenRecordingState {}
+
+    /** Indicates the app that registered the callback is not visible in screen recording. */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;
+
+    /** Indicates the app that registered the callback is visible in screen recording. */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    int SCREEN_RECORDING_STATE_VISIBLE = 1;
+
+    /**
+     * Adds a screen recording callback. The callback will be invoked whenever the app becomes
+     * visible in screen recording or was visible in screen recording and becomes invisible in
+     * screen recording.
+     *
+     * <p>An app is considered visible in screen recording if any activities owned by the
+     * registering process's UID are being recorded.
+     *
+     * <p>Example:
+     *
+     * <pre>
+     * windowManager.addScreenRecordingCallback(state -> {
+     *     // handle change in screen recording state
+     * });
+     * </pre>
+     *
+     * @param executor The executor on which callback method will be invoked.
+     * @param callback The callback that will be invoked when screen recording visibility changes.
+     * @return the current screen recording state.
+     * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
+     * @see #SCREEN_RECORDING_STATE_VISIBLE
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    default @ScreenRecordingState int addScreenRecordingCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Removes a screen recording callback.
+     *
+     * @param callback The callback to remove.
+     * @see #addScreenRecordingCallback(Executor, Consumer)
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    default void removeScreenRecordingCallback(
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5072ad7..eaf45c4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -20,6 +20,8 @@
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.window.WindowProviderService.isWindowProviderService;
 
+import static com.android.window.flags.Flags.screenRecordingCallbacks;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -551,4 +553,25 @@
     public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
         return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
     }
+
+    @Override
+    public @ScreenRecordingState int addScreenRecordingCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        if (screenRecordingCallbacks()) {
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(callback, "callback must not be null");
+            return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
+        }
+        return SCREEN_RECORDING_STATE_NOT_VISIBLE;
+    }
+
+    @Override
+    public void removeScreenRecordingCallback(
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        if (screenRecordingCallbacks()) {
+            Objects.requireNonNull(callback, "callback must not be null");
+            ScreenRecordingCallbacks.getInstance().removeCallback(callback);
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ef1bf5a..0deaca1 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -67,7 +67,6 @@
 import android.view.accessibility.AccessibilityEvent.EventType;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IntPair;
 
@@ -157,6 +156,22 @@
     public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
             "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
 
+    /**
+     * Used as an int value for accessibility chooser activity to represent the accessibility button
+     * shortcut type.
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_BUTTON = 0;
+
+    /**
+     * Used as an int value for accessibility chooser activity to represent hardware key shortcut,
+     * such as volume key button.
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
+
     /** @hide */
     public static final int FLASH_REASON_CALL = 1;
 
@@ -170,6 +185,32 @@
     public static final int FLASH_REASON_PREVIEW = 4;
 
     /**
+     * Annotations for the shortcut type.
+     * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            // LINT.IfChange(shortcut_type_intdef)
+            ACCESSIBILITY_BUTTON,
+            ACCESSIBILITY_SHORTCUT_KEY
+            // LINT.ThenChange(:shortcut_type_array)
+    })
+    public @interface ShortcutType {}
+
+    /**
+     * Used for iterating through {@link ShortcutType}.
+     * <p>Note: Keep in sync with {@link ShortcutType}.</p>
+     * @hide
+     */
+    public static final int[] SHORTCUT_TYPES = {
+            // LINT.IfChange(shortcut_type_array)
+            ACCESSIBILITY_BUTTON,
+            ACCESSIBILITY_SHORTCUT_KEY,
+            // LINT.ThenChange(:shortcut_type_intdef)
+    };
+
+    /**
      * Annotations for content flag of UI.
      * @hide
      */
@@ -1745,8 +1786,7 @@
     @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
     @NonNull
-    public List<String> getAccessibilityShortcutTargets(
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+    public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -2424,7 +2464,7 @@
             }
         }
         try {
-            service.attachAccessibilityOverlayToDisplay_enforcePermission(
+            service.attachAccessibilityOverlayToDisplay(
                     displayId, surfaceControl);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1c5d29e..eca1586 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -139,5 +139,5 @@
     WindowTransformationSpec getWindowTransformationSpec(int windowId);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
-    void attachAccessibilityOverlayToDisplay_enforcePermission(int displayId, in SurfaceControl surfaceControl);
+    void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
 }
diff --git a/core/java/android/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
index 72518db..1755497 100644
--- a/core/java/android/view/accessibility/MagnificationAnimationCallback.java
+++ b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
@@ -16,6 +16,8 @@
 
 package android.view.accessibility;
 
+import android.view.MagnificationSpec;
+
 /**
  * A callback for magnification animation result.
  * @hide
@@ -31,4 +33,16 @@
      *                change. Otherwise {@code false}
      */
     void onResult(boolean success);
+
+    /**
+     * Called when the animation is finished or interrupted during animating.
+     *
+     * @param success {@code true} if animating successfully with given spec or the spec did not
+     *                change. Otherwise {@code false}
+     * @param lastSpecSent the last spec that was sent to WindowManager for animation, in case you
+     *                     need to update the local copy
+     */
+    default void onResult(boolean success, MagnificationSpec lastSpecSent) {
+        onResult(success);
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 0aa516e..9d613bc 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -50,4 +50,28 @@
     description: "Feature flag for toolkit metrics collecting for frame rate decision"
     bug: "301343249"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_default_normal_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for setting frame rate category as NORMAL for default"
+    bug: "239979904"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_by_size_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for setting frame rate category based on size"
+    bug: "239979904"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_velocity_mapping_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for setting frame rate based on velocity"
+    bug: "239979904"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c6271d2..2f765ae 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,7 +16,6 @@
 
 package android.webkit;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.Compatibility;
@@ -47,8 +46,7 @@
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
-    public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+    static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
 
     private static final String LOGTAG = "webkit";
     private static final boolean TRACE = false;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1fdd1a5..f54ef38 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -425,12 +425,12 @@
     int mMotionViewNewTop;
 
     /**
-     * The X value associated with the the down motion event
+     * The X value associated with the down motion event
      */
     int mMotionX;
 
     /**
-     * The Y value associated with the the down motion event
+     * The Y value associated with the down motion event
      */
     @UnsupportedAppUsage
     int mMotionY;
@@ -7381,7 +7381,7 @@
 
             scrap.dispatchStartTemporaryDetach();
 
-            // The the accessibility state of the view may change while temporary
+            // the accessibility state of the view may change while temporary
             // detached and we do not allow detached views to fire accessibility
             // events. So we are announcing that the subtree changed giving a chance
             // to clients holding on to a view in this subtree to refresh it.
@@ -7750,7 +7750,7 @@
     }
 
     /**
-     * Abstract positon scroller used to handle smooth scrolling.
+     * Abstract position scroller used to handle smooth scrolling.
      */
     static abstract class AbsPositionScroller {
         public abstract void start(int position);
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 76e97ad..3b7e1e9 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -170,7 +170,7 @@
      * @see android.view.View#measure(int, int)
      *
      * Figure out the dimensions of this Spinner. The width comes from
-     * the widthMeasureSpec as Spinnners can't have their width set to
+     * the widthMeasureSpec as Spinners can't have their width set to
      * UNSPECIFIED. The height is based on the height of the selected item
      * plus padding.
      */
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index e20357fa..1dc90ed 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -15,3 +15,5 @@
 per-file Remote* = file:../appwidget/OWNERS
 
 per-file Toast.java = juliacr@google.com, jeffdq@google.com
+
+per-file flags/notification_widget_flags.aconfig = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/android/widget/RemoteCanvas.java b/core/java/android/widget/RemoteCanvas.java
deleted file mode 100644
index 9a0898c..0000000
--- a/core/java/android/widget/RemoteCanvas.java
+++ /dev/null
@@ -1,117 +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.widget;
-
-import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
-
-import android.annotation.AttrRes;
-import android.annotation.FlaggedApi;
-import android.annotation.StyleRes;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import java.util.function.IntConsumer;
-
-/**
- * {@link RemoteCanvas} is designed to support arbitrary protocols between two processes using
- * {@link RemoteViews.DrawInstructions}. Upon instantiation in the host process,
- * {@link RemoteCanvas#setDrawInstructions(RemoteViews.DrawInstructions)} is called so that the
- * host process can render the {@link RemoteViews.DrawInstructions} from the provider process
- * accordingly.
- *
- * @hide
- */
-@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
-public class RemoteCanvas extends View {
-
-    private static final String TAG = "RemoteCanvas";
-
-    @Nullable
-    private SparseArray<Runnable> mCallbacks;
-
-    private final IntConsumer mOnClickHandler = (viewId) -> {
-        if (mCallbacks == null) {
-            Log.w(TAG, "Cannot find callback for " + viewId
-                    + ", in fact there were no callbacks from this RemoteViews at all.");
-            return;
-        }
-        final Runnable cb = getCallbacks().get(viewId);
-        if (cb != null) {
-            cb.run();
-        } else {
-            Log.w(TAG, "Cannot find callback for " + viewId);
-        }
-    };
-
-    RemoteCanvas(@NonNull Context context) {
-        super(context);
-    }
-
-    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
-                 @AttrRes int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
-                 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * Setter method for the {@link RemoteViews.DrawInstructions} from the provider process for
-     * the host process to render accordingly.
-     *
-     * @param instructions {@link RemoteViews.DrawInstructions} from the provider process.
-     */
-    void setDrawInstructions(@NonNull final RemoteViews.DrawInstructions instructions) {
-        setTag(instructions);
-        // TODO: handle draw instructions
-        // TODO: attach mOnClickHandler
-    }
-
-    /**
-     * Adds a callback function to a clickable area in the RemoteCanvas.
-     *
-     * @param viewId the viewId of the clickable area
-     * @param cb the callback function to be triggered when clicked
-     */
-    void addOnClickHandler(final int viewId, @NonNull final Runnable cb) {
-        getCallbacks().set(viewId, cb);
-    }
-
-    /**
-     * Returns all callbacks added to the RemoteCanvas through
-     * {@link #addOnClickHandler(int, Runnable)}.
-     */
-    @VisibleForTesting
-    public SparseArray<Runnable> getCallbacks() {
-        if (mCallbacks == null) {
-            mCallbacks = new SparseArray<>();
-        }
-        return mCallbacks;
-    }
-}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2433bd8..5bf1b5b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -112,7 +112,10 @@
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.IRemoteViewsFactory;
+import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
+import com.android.internal.widget.remotecompose.player.RemoteComposePlayer;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
@@ -1479,9 +1482,7 @@
 
         @Override
         public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
-            if (hasDrawInstructions() && root instanceof RemoteCanvas target) {
-                target.addOnClickHandler(mViewId, () ->
-                        mResponse.handleViewInteraction(root, params.handler));
+            if (hasDrawInstructions() && root instanceof RemoteComposePlayer) {
                 return;
             }
             final View target = root.findViewById(mViewId);
@@ -3900,8 +3901,17 @@
         public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
                 throws ActionException {
             if (drawDataParcel() && mInstructions != null
-                    && root instanceof RemoteCanvas remoteCanvas) {
-                remoteCanvas.setDrawInstructions(mInstructions);
+                    && root instanceof RemoteComposePlayer player) {
+                player.setTag(mInstructions);
+                final List<byte[]> bytes = mInstructions.mInstructions;
+                if (bytes.isEmpty()) {
+                    return;
+                }
+                try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
+                    player.setDocument(new RemoteComposeDocument(is));
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "Failed to render draw instructions", e);
+                }
             }
         }
 
@@ -5044,10 +5054,7 @@
      * @param viewId The id of the {@link AdapterView}
      * @param intent The intent of the service which will be
      *            providing data to the RemoteViewsAdapter
-     * @deprecated use
-     * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
      */
-    @Deprecated
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
         if (remoteAdapterConversion()) {
             addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
@@ -6044,6 +6051,16 @@
         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
         View result = inflateView(context, rvToApply, directParent,
                 params.applyThemeResId, params.colorResources);
+        if (result instanceof RemoteComposePlayer player) {
+            player.addClickListener((viewId, metadata) -> {
+                mActions.forEach(action -> {
+                    if (viewId == action.mViewId
+                            && action instanceof SetOnClickResponse setOnClickResponse) {
+                        setOnClickResponse.mResponse.handleViewInteraction(player, params.handler);
+                    }
+                });
+            });
+        }
         rvToApply.performApply(result, rootParent, params);
         return result;
     }
@@ -6067,7 +6084,7 @@
         }
         // If the RemoteViews contains draw instructions, just use it instead.
         if (rv.hasDrawInstructions()) {
-            return new RemoteCanvas(inflationContext);
+            return new RemoteComposePlayer(inflationContext);
         }
         LayoutInflater inflater = LayoutInflater.from(context);
 
@@ -7549,7 +7566,7 @@
     public static final class DrawInstructions {
 
         @NonNull
-        private final List<byte[]> mInstructions;
+        final List<byte[]> mInstructions;
 
         private DrawInstructions() {
             throw new UnsupportedOperationException(
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 90d5140..9a4106d9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1259,6 +1259,7 @@
         int lastBaselineToBottomHeight = -1;
         float lineHeight = -1f;
         int lineHeightUnit = -1;
+        boolean hasUseBoundForWidthValue = false;
 
         readTextAppearance(context, a, attributes, true /* styleArray */);
 
@@ -1613,6 +1614,9 @@
                         lineHeight = a.getDimensionPixelSize(attr, -1);
                     }
                     break;
+                case com.android.internal.R.styleable.TextView_useBoundsForWidth:
+                    mUseBoundsForWidth = a.getBoolean(attr, false);
+                    hasUseBoundForWidthValue = true;
             }
         }
 
@@ -1639,10 +1643,12 @@
             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
         }
 
-        if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
-            mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
-        } else {
-            mUseBoundsForWidth = false;
+        if (!hasUseBoundForWidthValue) {
+            if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
+                mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
+            } else {
+                mUseBoundsForWidth = false;
+            }
         }
 
         // TODO(b/179693024): Use a ChangeId instead.
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
new file mode 100644
index 0000000..9f0b7c3
--- /dev/null
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+   name: "notif_linearlayout_optimized"
+   namespace: "systemui"
+   description: "Enables notification specific LinearLayout optimization"
+   bug: "316110233"
+}
\ No newline at end of file
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 9ef6880..0cc9a0d 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -50,6 +50,8 @@
     static final int RESULT_CODE_UNREGISTER = 1;
     @NonNull
     private final ResultReceiver mResultReceiver;
+    @NonNull
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
 
     public ImeOnBackInvokedDispatcher(Handler handler) {
         mResultReceiver = new ResultReceiver(handler) {
@@ -88,7 +90,7 @@
         // cause a memory leak because the app side already clears the reference correctly.
         final IOnBackInvokedCallback iCallback =
                 new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(
-                        callback, false /* useWeakRef */);
+                        callback, mProgressAnimator, false /* useWeakRef */);
         bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
         bundle.putInt(RESULT_KEY_PRIORITY, priority);
         bundle.putInt(RESULT_KEY_ID, callback.hashCode());
@@ -179,6 +181,9 @@
             }
         }
         mImeCallbacks.clear();
+        // We should also stop running animations since all callbacks have been removed.
+        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+        Handler.getMain().post(mProgressAnimator::reset);
     }
 
     static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index cc875ad..c76c7a4 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -21,6 +21,7 @@
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
 import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -93,7 +94,8 @@
             | FLAG_WATCH_OUTSIDE_TOUCH
             | FLAG_SPLIT_TOUCH
             | FLAG_SCALED
-            | FLAG_SECURE;
+            | FLAG_SECURE
+            | FLAG_DIM_BEHIND;
 
     private static final RectF sTmpSnapshotSize = new RectF();
     private static final RectF sTmpDstFrame = new RectF();
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index e32c8e5..739cf0e 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.Surface.ROTATION_0;
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 
@@ -31,6 +32,7 @@
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.view.Display;
+import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
 import android.view.WindowInsets;
@@ -157,10 +159,15 @@
                     new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
                             currentDisplayInfo.getNaturalHeight()), isScreenRound,
                     ACTIVITY_TYPE_UNDEFINED);
-            // Set the hardware-provided insets.
+            // Set the hardware-provided insets. Always with the ROTATION_0 result.
+            DisplayCutout cutout = currentDisplayInfo.displayCutout;
+            if (cutout != null && currentDisplayInfo.rotation != ROTATION_0) {
+                cutout = cutout.getRotated(
+                        currentDisplayInfo.logicalWidth, currentDisplayInfo.logicalHeight,
+                        currentDisplayInfo.rotation, ROTATION_0);
+            }
             windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
-                            currentDisplayInfo.roundedCorners)
-                    .setDisplayCutout(currentDisplayInfo.displayCutout).build();
+                    currentDisplayInfo.roundedCorners).setDisplayCutout(cutout).build();
 
             // Multiply default density scale because WindowMetrics provide the density value with
             // the scaling factor for the Density Independent Pixel unit, which is the same unit
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 65075ae..5c911f4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,6 +226,9 @@
             setTopOnBackInvokedCallback(null);
         }
 
+        // We should also stop running animations since all callbacks have been removed.
+        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+        Handler.getMain().post(mProgressAnimator::reset);
         mAllCallbacks.clear();
         mOnBackInvokedCallbacks.clear();
     }
@@ -243,7 +246,10 @@
                                     .ImeOnBackInvokedCallback
                                 ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
                                         callback).getIOnBackInvokedCallback()
-                                : new OnBackInvokedCallbackWrapper(callback, this);
+                                : new OnBackInvokedCallbackWrapper(
+                                        callback,
+                                        mProgressAnimator,
+                                        this);
                 callbackInfo = new OnBackInvokedCallbackInfo(
                         iCallback,
                         priority,
@@ -269,7 +275,7 @@
     }
 
     @NonNull
-    private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
     private boolean mIsDispatching = false;
 
     /**
@@ -336,18 +342,24 @@
          * forwarded and registered on the app's {@link WindowOnBackInvokedDispatcher}. */
         @Nullable
         private final WindowOnBackInvokedDispatcher mDispatcher;
+        @NonNull
+        private final BackProgressAnimator mProgressAnimator;
 
         OnBackInvokedCallbackWrapper(
                 @NonNull OnBackInvokedCallback callback,
+                @NonNull BackProgressAnimator progressAnimator,
                 WindowOnBackInvokedDispatcher dispatcher) {
             mCallbackRef = new CallbackRef(callback, true /* useWeakRef */);
+            mProgressAnimator = progressAnimator;
             mDispatcher = dispatcher;
         }
 
         OnBackInvokedCallbackWrapper(
                 @NonNull OnBackInvokedCallback callback,
+                @NonNull BackProgressAnimator progressAnimator,
                 boolean useWeakRef) {
             mCallbackRef = new CallbackRef(callback, useWeakRef);
+            mProgressAnimator = progressAnimator;
             mDispatcher = null;
         }
 
@@ -359,11 +371,11 @@
                 }
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
-                    mProgressAnimator.onBackStarted(backEvent, event ->
-                            callback.onBackProgressed(event));
                     callback.onBackStarted(new BackEvent(
                             backEvent.getTouchX(), backEvent.getTouchY(),
                             backEvent.getProgress(), backEvent.getSwipeEdge()));
+                    mProgressAnimator.onBackStarted(backEvent, event ->
+                            callback.onBackProgressed(event));
                 }
             });
         }
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 1de77f6..82067de 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
@@ -66,3 +66,14 @@
   bug: "314952133"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "app_compat_refactoring"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the changes about app compat refactoring are enabled./n"
+                 "The goal is to simplify code readability unblocking the implementation of /n"
+                 "app compat feature like reachability, animations and others related to/n"
+                 "freeform windowing mode."
+  bug: "309593314"
+  is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index f234637..14fb17c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -84,6 +84,14 @@
 }
 
 flag {
+  name: "delegate_unhandled_drags"
+  namespace: "multitasking"
+  description: "Enables delegating unhandled drags to SystemUI"
+  bug: "320797628"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "insets_decoupled_configuration"
   namespace: "windowing_frontend"
   description: "Configuration decoupled from insets"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4a6e8d7..bc63881 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -16,7 +16,7 @@
     namespace: "windowing_sdk"
     name: "activity_embedding_overlay_presentation_flag"
     description: "Whether the overlay presentation feature is enabled"
-    bug: "243518738"
+    bug: "293370683"
 }
 
 flag {
@@ -30,7 +30,7 @@
     namespace: "windowing_sdk"
     name: "fullscreen_dim_flag"
     description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
-    bug: "253533308"
+    bug: "293797706"
 }
 
 flag {
@@ -44,7 +44,7 @@
     namespace: "windowing_sdk"
     name: "untrusted_embedding_any_app_permission"
     description: "Feature flag to enable the permission to embed any app in untrusted mode."
-    bug: "289199433"
+    bug: "293647332"
     is_fixed_read_only: true
 }
 
@@ -58,7 +58,15 @@
 
 flag {
     namespace: "windowing_sdk"
+    name: "untrusted_embedding_state_sharing"
+    description: "Feature flag to enable state sharing in untrusted embedding when apps opt in."
+    bug: "293647332"
+    is_fixed_read_only: true
+}
+
+flag {
+    namespace: "windowing_sdk"
     name: "embedded_activity_back_nav_flag"
     description: "Refines embedded activity back navigation behavior"
-    bug: "240575809"
+    bug: "293642394"
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index b4395a7..de0f070 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -18,6 +18,7 @@
 
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
@@ -328,8 +329,7 @@
     }
 
     private AlertDialog createShortcutWarningDialog(int userId) {
-        List<AccessibilityTarget> targets = getTargets(mContext,
-                ShortcutConstants.UserShortcutType.HARDWARE);
+        List<AccessibilityTarget> targets = getTargets(mContext, ACCESSIBILITY_SHORTCUT_KEY);
         if (targets.size() == 0) {
             return null;
         }
@@ -541,7 +541,7 @@
     private ComponentName getShortcutTargetComponentName() {
         final List<String> shortcutTargets = mFrameworkObjectProvider
                 .getAccessibilityManagerInstance(mContext)
-                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE);
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
         if (shortcutTargets.size() != 1) {
             return null;
         }
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 353e182..7ec8838 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -44,27 +44,19 @@
      * choose accessibility shortcut as preferred shortcut.
      * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
      * tapping screen 3 times as preferred shortcut.
-     * {@code TWO_FINGERS_TRIPLE_TAP} for displaying specifying magnification to be toggled via
-     * quickly tapping screen 3 times with two fingers as preferred shortcut.
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            flag = true,
-            value = {
-                    UserShortcutType.DEFAULT,
-                    UserShortcutType.SOFTWARE,
-                    UserShortcutType.HARDWARE,
-                    UserShortcutType.TRIPLETAP,
-                    UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
-            })
+    @IntDef({
+            UserShortcutType.DEFAULT,
+            UserShortcutType.SOFTWARE,
+            UserShortcutType.HARDWARE,
+            UserShortcutType.TRIPLETAP,
+    })
     public @interface UserShortcutType {
         int DEFAULT = 0;
-        // LINT.IfChange(shortcut_type_intdef)
-        int SOFTWARE = 1;
-        int HARDWARE = 1 << 1;
-        int TRIPLETAP = 1 << 2;
-        int TWO_FINGERS_TRIPLE_TAP = 1 << 3;
-        // LINT.ThenChange(:shortcut_type_array)
+        int SOFTWARE = 1; // 1 << 0
+        int HARDWARE = 2; // 1 << 1
+        int TRIPLETAP = 4; // 1 << 2
     }
 
     /**
@@ -72,12 +64,9 @@
      * non-default IntDef types.
      */
     public static final int[] USER_SHORTCUT_TYPES = {
-            // LINT.IfChange(shortcut_type_array)
             UserShortcutType.SOFTWARE,
             UserShortcutType.HARDWARE,
-            UserShortcutType.TRIPLETAP,
-            UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
-            // LINT.ThenChange(:shortcut_type_intdef)
+            UserShortcutType.TRIPLETAP
     };
 
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
index 33048dc..063154d 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -17,13 +17,14 @@
 package com.android.internal.accessibility.dialog;
 
 import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
 
 import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 
@@ -32,8 +33,7 @@
  */
 class AccessibilityActivityTarget extends AccessibilityTarget {
 
-    AccessibilityActivityTarget(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType,
+    AccessibilityActivityTarget(Context context, @ShortcutType int shortcutType,
             @NonNull AccessibilityShortcutInfo shortcutInfo) {
         super(context,
                 shortcutType,
@@ -44,7 +44,7 @@
                 shortcutInfo.getActivityInfo().applicationInfo.uid,
                 shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
                 shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
-                convertToKey(shortcutType));
+                convertToKey(convertToUserType(shortcutType)));
     }
 
     @Override
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index e084ebd..7eb09e5 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -17,6 +17,7 @@
 package com.android.internal.accessibility.dialog;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -35,7 +36,6 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.widget.ResolverDrawerLayout;
 
 import java.util.ArrayList;
@@ -85,7 +85,7 @@
             prompt.setVisibility(View.VISIBLE);
         }
 
-        mTargets.addAll(getTargets(this, ShortcutConstants.UserShortcutType.SOFTWARE));
+        mTargets.addAll(getTargets(this, ACCESSIBILITY_BUTTON));
 
         final GridView gridview = findViewById(R.id.accessibility_button_chooser_grid);
         gridview.setAdapter(new ButtonTargetAdapter(mTargets));
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index 7406da4..2b6913c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -17,13 +17,14 @@
 package com.android.internal.accessibility.dialog;
 
 import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 
@@ -35,9 +36,7 @@
 
     private final AccessibilityServiceInfo mAccessibilityServiceInfo;
 
-    AccessibilityServiceTarget(
-            Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType,
+    AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
             @AccessibilityFragmentType int fragmentType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
@@ -49,7 +48,7 @@
                 serviceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
                 serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
                 serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
-                convertToKey(shortcutType));
+                convertToKey(convertToUserType(shortcutType)));
         mAccessibilityServiceInfo = serviceInfo;
     }
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 8e2ec1b..2e80b7e 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -15,6 +15,10 @@
  */
 package com.android.internal.accessibility.dialog;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
+
 import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
@@ -38,7 +42,6 @@
 import android.widget.AdapterView;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -49,8 +52,8 @@
  * activity or allowlisting feature for volume key shortcut.
  */
 public class AccessibilityShortcutChooserActivity extends Activity {
-    @ShortcutConstants.UserShortcutType
-    private final int mShortcutType = ShortcutConstants.UserShortcutType.HARDWARE;
+    @ShortcutType
+    private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
     private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
             "accessibility_shortcut_menu_mode";
     private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@@ -243,7 +246,7 @@
                 mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
         final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
         final int editDialogTitleId =
-                mShortcutType == ShortcutConstants.UserShortcutType.SOFTWARE
+                mShortcutType == ACCESSIBILITY_BUTTON
                         ? R.string.accessibility_edit_shortcut_menu_button_title
                         : R.string.accessibility_edit_shortcut_menu_volume_title;
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index 4ab1ee9..652cb52 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.accessibility.dialog;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
+import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
 import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
 import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
 
@@ -26,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -42,7 +47,7 @@
 public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
         OnTargetCheckedChangeListener {
     private Context mContext;
-    @ShortcutConstants.UserShortcutType
+    @ShortcutType
     private int mShortcutType;
     @AccessibilityFragmentType
     private int mFragmentType;
@@ -56,8 +61,7 @@
     private CharSequence mStateDescription;
 
     @VisibleForTesting
-    public AccessibilityTarget(
-            Context context, @ShortcutConstants.UserShortcutType int shortcutType,
+    public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
             @AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
             int uid, CharSequence label, Drawable icon, String key) {
         mContext = context;
@@ -95,10 +99,10 @@
         final AccessibilityManager am =
                 getContext().getSystemService(AccessibilityManager.class);
         switch (getShortcutType()) {
-            case ShortcutConstants.UserShortcutType.SOFTWARE:
+            case ACCESSIBILITY_BUTTON:
                 am.notifyAccessibilityButtonClicked(getContext().getDisplayId(), getId());
                 return;
-            case ShortcutConstants.UserShortcutType.HARDWARE:
+            case ACCESSIBILITY_SHORTCUT_KEY:
                 am.performAccessibilityShortcut(getId());
                 return;
             default:
@@ -110,9 +114,9 @@
     public void onCheckedChanged(boolean isChecked) {
         setShortcutEnabled(isChecked);
         if (isChecked) {
-            optInValueToSettings(getContext(), getShortcutType(), getId());
+            optInValueToSettings(getContext(), convertToUserType(getShortcutType()), getId());
         } else {
-            optOutValueFromSettings(getContext(), getShortcutType(), getId());
+            optOutValueFromSettings(getContext(), convertToUserType(getShortcutType()), getId());
         }
     }
 
@@ -138,7 +142,7 @@
         return mContext;
     }
 
-    public @ShortcutConstants.UserShortcutType int getShortcutType() {
+    public @ShortcutType int getShortcutType() {
         return mShortcutType;
     }
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index bd63e23..51a5ddf 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.accessibility.dialog;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
@@ -39,12 +41,12 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 
 import java.util.ArrayList;
@@ -68,9 +70,8 @@
      * @return The list of {@link AccessibilityTarget}.
      * @hide
      */
-    public static List<AccessibilityTarget> getTargets(
-            Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+    public static List<AccessibilityTarget> getTargets(Context context,
+            @ShortcutType int shortcutType) {
         // List all accessibility target
         final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
                 shortcutType);
@@ -112,7 +113,7 @@
      * @return The list of {@link AccessibilityTarget}.
      */
     static List<AccessibilityTarget> getInstalledTargets(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
         targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
         targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
@@ -121,7 +122,7 @@
     }
 
     private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> serviceTargets =
                 getAccessibilityServiceTargets(context, shortcutType);
         final List<AccessibilityTarget> activityTargets =
@@ -154,7 +155,7 @@
     }
 
     private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> installedServices =
@@ -170,7 +171,7 @@
             final boolean hasRequestAccessibilityButtonFlag =
                     (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
-                    && (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE)) {
+                    && (shortcutType == ACCESSIBILITY_BUTTON)) {
                 continue;
             }
 
@@ -181,7 +182,7 @@
     }
 
     private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityShortcutInfo> installedServices =
@@ -200,7 +201,7 @@
     }
 
     private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
         final int uid = context.getApplicationInfo().uid;
 
@@ -280,10 +281,8 @@
         return targets;
     }
 
-    private static AccessibilityTarget createAccessibilityServiceTarget(
-            Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType,
-            @NonNull AccessibilityServiceInfo info) {
+    private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
+            @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
         switch (getAccessibilityServiceFragmentType(info)) {
             case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
                 return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
index 641a9f1..1bc8b84 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.accessibility.dialog;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
@@ -25,6 +28,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.UserHandle;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 import android.view.accessibility.Flags;
 
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -41,7 +45,7 @@
 public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
 
     public InvisibleToggleAccessibilityServiceTarget(
-            Context context, @UserShortcutType int shortcutType,
+            Context context, @ShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
                 shortcutType,
@@ -68,10 +72,10 @@
 
     private boolean isComponentIdExistingInOtherShortcut() {
         switch (getShortcutType()) {
-            case UserShortcutType.SOFTWARE:
+            case ACCESSIBILITY_BUTTON:
                 return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
                         getId());
-            case UserShortcutType.HARDWARE:
+            case ACCESSIBILITY_SHORTCUT_KEY:
                 return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
                         getId());
             default:
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index 2204c0b..c22f17d 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 
 /**
@@ -28,8 +28,7 @@
  */
 class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
-    InvisibleToggleAllowListingFeatureTarget(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType,
+    InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
             boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
             String key) {
         super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE, isShortcutSwitched,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index a6ef73e..a4ffef6 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -22,9 +22,9 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -45,8 +45,7 @@
         float DISABLED = 0.5f;
     }
 
-    ToggleAccessibilityServiceTarget(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType,
+    ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
                 shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 2a9c555..11e668f 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -21,9 +21,9 @@
 import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.R;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -34,8 +34,7 @@
  */
 class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
-    ToggleAllowListingFeatureTarget(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType,
+    ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
             boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
             String key) {
         super(context, shortcutType, AccessibilityFragmentType.TOGGLE, isShortcutSwitched, id,
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 4926e72..04f5061 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.accessibility.dialog;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
@@ -24,6 +27,7 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -35,8 +39,7 @@
  */
 class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
 
-    VolumeShortcutToggleAccessibilityServiceTarget(Context context,
-            @UserShortcutType int shortcutType,
+    VolumeShortcutToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
                 shortcutType,
@@ -47,10 +50,10 @@
     @Override
     public void onCheckedChanged(boolean isChecked) {
         switch (getShortcutType()) {
-            case UserShortcutType.SOFTWARE:
+            case ACCESSIBILITY_BUTTON:
                 onCheckedFromAccessibilityButton(isChecked);
                 return;
-            case UserShortcutType.HARDWARE:
+            case ACCESSIBILITY_SHORTCUT_KEY:
                 super.onCheckedChanged(isChecked);
                 return;
             default:
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 1e4bcf2..6b074a6 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -21,6 +21,8 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
@@ -45,8 +47,9 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.util.FrameworkStatsLog;
 
 /** Methods for logging accessibility states. */
@@ -68,15 +71,15 @@
 
     /**
      * Logs accessibility feature name that is assigned to the given {@code shortcutType}.
-     * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE} or
-     * {@link ShortcutConstants.UserShortcutType.HARDWARE}.
+     * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON} or
+     * {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
      *
      * @param context context used to retrieve the {@link Settings} provider
      * @param componentName component name of the accessibility feature
      * @param shortcutType  accessibility shortcut type
      */
     public static void logAccessibilityShortcutActivated(Context context,
-            ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType) {
+            ComponentName componentName, @ShortcutType int shortcutType) {
         logAccessibilityShortcutActivatedInternal(componentName,
                 convertToLoggingShortcutType(context, shortcutType), UNKNOWN_STATUS);
     }
@@ -84,8 +87,8 @@
     /**
      * Logs accessibility feature name that is assigned to the given {@code shortcutType} and the
      * {@code serviceEnabled} status.
-     * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE}
-     * or {@link ShortcutConstants.UserShortcutType.HARDWARE}.
+     * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON}
+     * or {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
      *
      * @param context context used to retrieve the {@link Settings} provider
      * @param componentName  component name of the accessibility feature
@@ -93,8 +96,7 @@
      * @param serviceEnabled {@code true} if the service is enabled
      */
     public static void logAccessibilityShortcutActivated(Context context,
-            ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType,
-            boolean serviceEnabled) {
+            ComponentName componentName, @ShortcutType int shortcutType, boolean serviceEnabled) {
         logAccessibilityShortcutActivatedInternal(componentName,
                 convertToLoggingShortcutType(context, shortcutType),
                 convertToLoggingServiceStatus(serviceEnabled));
@@ -233,9 +235,9 @@
     }
 
     private static int convertToLoggingShortcutType(Context context,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         switch (shortcutType) {
-            case ShortcutConstants.UserShortcutType.SOFTWARE:
+            case ACCESSIBILITY_BUTTON:
                 if (isAccessibilityFloatingMenuEnabled(context)) {
                     return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
                 } else if (isAccessibilityGestureEnabled(context)) {
@@ -243,7 +245,7 @@
                 } else {
                     return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
                 }
-            case ShortcutConstants.UserShortcutType.HARDWARE:
+            case ACCESSIBILITY_SHORTCUT_KEY:
                 return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
         }
         return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 276c5c4..3fd3030 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.accessibility.util;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
@@ -30,6 +33,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import java.util.Collections;
 import java.util.List;
@@ -140,7 +144,7 @@
      * @param componentId The component id that need to be checked.
      * @return {@code true} if a component id is contained.
      */
-    public static boolean isShortcutContained(Context context, @UserShortcutType int shortcutType,
+    public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
             @NonNull String componentId) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
@@ -162,8 +166,6 @@
                 return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
             case UserShortcutType.TRIPLETAP:
                 return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
-            case UserShortcutType.TWO_FINGERS_TRIPLE_TAP:
-                return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
             default:
                 throw new IllegalArgumentException(
                         "Unsupported user shortcut type: " + type);
@@ -171,6 +173,24 @@
     }
 
     /**
+     * Converts {@link ShortcutType} to {@link UserShortcutType}.
+     *
+     * @param type The shortcut type.
+     * @return Mapping type from {@link UserShortcutType}.
+     */
+    public static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+        switch (type) {
+            case ACCESSIBILITY_BUTTON:
+                return UserShortcutType.SOFTWARE;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                return UserShortcutType.HARDWARE;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported shortcut type:" + type);
+        }
+    }
+
+    /**
      * Updates an accessibility state if the accessibility service is a Always-On a11y service,
      * a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
      * <p>
@@ -235,13 +255,12 @@
     public static Set<String> getShortcutTargetsFromSettings(
             Context context, @UserShortcutType int shortcutType, int userId) {
         final String targetKey = convertToKey(shortcutType);
-        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)
-                || Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED
-                .equals(targetKey)) {
+        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
             boolean magnificationEnabled = Settings.Secure.getIntForUser(
                     context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
             return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
                     : Collections.emptySet();
+
         } else {
             final String targetString = Settings.Secure.getStringForUser(
                     context.getContentResolver(), targetKey, userId);
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 83acc47..d433ca3 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -227,6 +227,9 @@
     private String requiredAccountType;
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
+    private String mEmergencyInstaller;
+    @Nullable
+    @DataClass.ParcelWith(ForInternedString.class)
     private String overlayTarget;
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -1275,6 +1278,12 @@
         return restrictedAccountType;
     }
 
+    @Nullable
+    @Override
+    public String getEmergencyInstaller() {
+        return mEmergencyInstaller;
+    }
+
     @Override
     public int getRoundIconResourceId() {
         return roundIconRes;
@@ -2336,6 +2345,12 @@
     }
 
     @Override
+    public PackageImpl setEmergencyInstaller(@Nullable String emergencyInstaller) {
+        this.mEmergencyInstaller = emergencyInstaller;
+        return this;
+    }
+
+    @Override
     public PackageImpl setRoundIconResourceId(int value) {
         roundIconRes = value;
         return this;
@@ -3105,6 +3120,7 @@
         dest.writeString(this.mBaseApkPath);
         dest.writeString(this.restrictedAccountType);
         dest.writeString(this.requiredAccountType);
+        dest.writeString(this.mEmergencyInstaller);
         sForInternedString.parcel(this.overlayTarget, dest, flags);
         dest.writeString(this.overlayTargetOverlayableName);
         dest.writeString(this.overlayCategory);
@@ -3255,6 +3271,7 @@
         this.mBaseApkPath = in.readString();
         this.restrictedAccountType = in.readString();
         this.requiredAccountType = in.readString();
+        this.mEmergencyInstaller = in.readString();
         this.overlayTarget = sForInternedString.unparcel(in);
         this.overlayTargetOverlayableName = in.readString();
         this.overlayCategory = in.readString();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index 7ef0b48..66cfb69 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -75,6 +75,11 @@
 
     ParsedPackage setUpdatableSystem(boolean value);
 
+    /**
+     * Sets a system app that is allowed to update another system app
+     */
+    ParsedPackage setEmergencyInstaller(String emergencyInstaller);
+
     ParsedPackage markNotActivitiesAsNotExportedIfSingleUser();
 
     ParsedPackage setOdm(boolean odm);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
index e5247f9..852ed1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
@@ -51,4 +51,6 @@
 
     @ApplicationInfo.NativeHeapZeroInitialized
     int getNativeHeapZeroInitialized();
+
+    boolean isUseEmbeddedDex();
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
index 212fb86..ff9b11a 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
@@ -54,6 +54,8 @@
     @ApplicationInfo.NativeHeapZeroInitialized
     private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
 
+    private boolean useEmbeddedDex;
+
     public ParsedProcessImpl() {
     }
 
@@ -65,6 +67,7 @@
         gwpAsanMode = other.getGwpAsanMode();
         memtagMode = other.getMemtagMode();
         nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+        useEmbeddedDex = other.isUseEmbeddedDex();
     }
 
     public void addStateFrom(@NonNull ParsedProcess other) {
@@ -72,6 +75,7 @@
         gwpAsanMode = other.getGwpAsanMode();
         memtagMode = other.getMemtagMode();
         nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+        useEmbeddedDex = other.isUseEmbeddedDex();
 
         final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
         for (int i = 0; i < oacn.size(); i++) {
@@ -115,7 +119,8 @@
             @NonNull Set<String> deniedPermissions,
             @ApplicationInfo.GwpAsanMode int gwpAsanMode,
             @ApplicationInfo.MemtagMode int memtagMode,
-            @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+            @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+            boolean useEmbeddedDex) {
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
@@ -134,6 +139,7 @@
         this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
         com.android.internal.util.AnnotationValidations.validate(
                 ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+        this.useEmbeddedDex = useEmbeddedDex;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -172,6 +178,11 @@
     }
 
     @DataClass.Generated.Member
+    public boolean isUseEmbeddedDex() {
+        return useEmbeddedDex;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull ParsedProcessImpl setName(@NonNull String value) {
         name = value;
         com.android.internal.util.AnnotationValidations.validate(
@@ -223,6 +234,12 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull ParsedProcessImpl setUseEmbeddedDex( boolean value) {
+        useEmbeddedDex = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
     static Parcelling<Set<String>> sParcellingForDeniedPermissions =
             Parcelling.Cache.get(
                     Parcelling.BuiltIn.ForInternedStringSet.class);
@@ -239,6 +256,9 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
+        byte flg = 0;
+        if (useEmbeddedDex) flg |= 0x40;
+        dest.writeByte(flg);
         dest.writeString(name);
         dest.writeMap(appClassNamesByPackage);
         sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
@@ -258,6 +278,8 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
+        byte flg = in.readByte();
+        boolean _useEmbeddedDex = (flg & 0x40) != 0;
         String _name = in.readString();
         ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
         in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
@@ -284,6 +306,7 @@
         this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
         com.android.internal.util.AnnotationValidations.validate(
                 ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+        this.useEmbeddedDex = _useEmbeddedDex;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -303,10 +326,10 @@
     };
 
     @DataClass.Generated(
-            time = 1701445656489L,
+            time = 1706177189475L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nprivate  boolean useEmbeddedDex\npublic  void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
index 3b2056e..dd58815f 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
@@ -27,6 +27,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.R;
+import com.android.internal.pm.pkg.component.flags.Flags;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.CollectionUtils;
@@ -111,6 +112,12 @@
                 proc.setNativeHeapZeroInitialized(
                         v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
             }
+            if (Flags.enablePerProcessUseEmbeddedDexAttr()) {
+                proc.setUseEmbeddedDex(
+                        sa.getBoolean(R.styleable.AndroidManifestProcess_useEmbeddedDex, false));
+            } else {
+                proc.setUseEmbeddedDex(false);
+            }
         } finally {
             sa.recycle();
         }
diff --git a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
new file mode 100644
index 0000000..ea9abdb
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.pm.pkg.component.flags"
+
+flag {
+    name: "enable_per_process_use_embedded_dex_attr"
+    namespace: "machine_learning"
+    description: "This flag enables support for parsing per-process useEmbeddedDex attribute."
+    is_fixed_read_only: true
+    bug: "295870718"
+}
\ No newline at end of file
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 6c09b7c..ef106e0 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -347,6 +347,11 @@
 
     ParsingPackage setUpdatableSystem(boolean value);
 
+    /**
+     * Sets a system app that is allowed to update another system app
+     */
+    ParsingPackage setEmergencyInstaller(String emergencyInstaller);
+
     ParsingPackage setLargeScreensSupported(int supportsLargeScreens);
 
     ParsingPackage setNormalScreensSupported(int supportsNormalScreens);
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 f483597..e0fdbc6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -952,6 +952,8 @@
 
         final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,
                 "updatableSystem", true);
+        final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/,
+                "emergencyInstaller");
 
         pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
                         R.styleable.AndroidManifest_installLocation, sa))
@@ -959,7 +961,8 @@
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
                 .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
-                .setUpdatableSystem(updatableSystem);
+                .setUpdatableSystem(updatableSystem)
+                .setEmergencyInstaller(emergencyInstaller);
 
         boolean foundApp = false;
         final int depth = parser.getDepth();
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index ce6af49..5cda3f2 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,16 +16,30 @@
 
 package com.android.internal.widget;
 
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
 import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableWrapper;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.ImageSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.RemotableViewMethod;
 import android.widget.Button;
 import android.widget.RemoteViews;
@@ -43,6 +57,14 @@
     private final GradientDrawable mBackground;
     private boolean mPriority;
 
+    private int mInitialDrawablePadding;
+    private int mIconSize;
+
+    private Drawable mIconToGlue;
+    private CharSequence mLabelToGlue;
+    private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+    private boolean mGluePending;
+
     public EmphasizedNotificationButton(Context context) {
         this(context, null);
     }
@@ -58,10 +80,25 @@
     public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
         mRipple = (RippleDrawable) getBackground();
         mRipple.mutate();
         DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
         mBackground = (GradientDrawable) inset.getDrawable();
+
+        mIconSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.notification_actions_icon_drawable_size);
+
+        try (TypedArray typedArray = context.obtainStyledAttributes(
+                attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
+            mInitialDrawablePadding = typedArray.getDimensionPixelSize(
+                    android.R.styleable.TextView_drawablePadding, 0);
+        }
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "iconSize = " + mIconSize + "px, "
+                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
+        }
     }
 
     @RemotableViewMethod
@@ -95,19 +132,248 @@
         return () -> setImageDrawable(drawable);
     }
 
-    private void setImageDrawable(Drawable drawable) {
+    private void setImageDrawable(@Nullable Drawable drawable) {
         if (drawable != null) {
-            drawable.mutate();
-            drawable.setTintList(getTextColors());
-            drawable.setTintBlendMode(BlendMode.SRC_IN);
-            int iconSize = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.notification_actions_icon_drawable_size);
-            drawable.setBounds(0, 0, iconSize, iconSize);
+            prepareIcon(drawable);
         }
         setCompoundDrawablesRelative(drawable, null, null, null);
     }
 
     /**
+     * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
+     * with the text if the button is wider than needed and the text isn't start-aligned.
+     *
+     * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
+     * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
+     * the latter to work.
+     *
+     * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
+     * button is displayed.
+     */
+    @RemotableViewMethod(asyncImpl = "glueIconAsync")
+    public void glueIcon(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        setIconToGlue(drawable);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable glueIconAsync(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        return () -> setIconToGlue(drawable);
+    }
+
+    private void setIconToGlue(@Nullable Drawable icon) {
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
+            return;
+        }
+
+        prepareIcon(icon);
+
+        mIconToGlue = icon;
+        mGluePending = true;
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    private void prepareIcon(@NonNull Drawable drawable) {
+        drawable.mutate();
+        drawable.setTintList(getTextColors());
+        drawable.setTintBlendMode(BlendMode.SRC_IN);
+        drawable.setBounds(0, 0, mIconSize, mIconSize);
+    }
+
+    /**
+     * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
+     * with the text if the button is wider than needed and the text isn't start-aligned.
+     *
+     * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
+     * displayed.
+     */
+    @RemotableViewMethod(asyncImpl = "glueLabelAsync")
+    public void glueLabel(@Nullable CharSequence label) {
+        setLabelToGlue(label);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable glueLabelAsync(@Nullable CharSequence label) {
+        return () -> setLabelToGlue(label);
+    }
+
+    private void setLabelToGlue(@Nullable CharSequence label) {
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
+            return;
+        }
+
+        mLabelToGlue = label;
+        mGluePending = true;
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
+                    + "gluedLayoutDirection = " + mGluedLayoutDirection);
+        }
+
+        if (layoutDirection != mGluedLayoutDirection) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
+            }
+            mGluePending = true;
+        }
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    private void glueIconAndLabelIfNeeded() {
+        // Don't need to glue:
+
+        if (!mGluePending) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
+            }
+            return;
+        }
+
+        if (mIconToGlue == null && mLabelToGlue == null) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
+            }
+            mGluePending = false;
+            return;
+        }
+
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
+            return;
+        }
+
+        // Not ready to glue yet:
+
+        if (!isLayoutDirectionResolved()) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: "
+                        + "layout direction not resolved; doing nothing");
+            }
+            return;
+        }
+
+        // Ready to glue but don't have an icon *and* a label:
+        //
+        // (Note that this will *not* happen while the button is being initialized, since we won't
+        // be ready to glue. This can only happen if the button is initialized and displayed and
+        // *then* someone calls glueIcon or glueLabel.
+
+        if (mIconToGlue == null) {
+            Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
+            return;
+        }
+
+        if (mLabelToGlue == null) {
+            Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
+            return;
+        }
+
+        // Can't glue:
+
+        final int layoutDirection = getLayoutDirection();
+        if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
+            Log.e(TAG, "glueIconAndLabelIfNeeded: "
+                    + "resolved layout direction neither LTR nor RTL; "
+                    + "doing nothing");
+            return;
+        }
+
+        // No excuses left, let's glue it!
+
+        glueIconAndLabel(layoutDirection);
+
+        mGluePending = false;
+        mGluedLayoutDirection = layoutDirection;
+    }
+
+    // Unicode replacement character
+    private static final String IMAGE_SPAN_TEXT = "\ufffd";
+
+    // Unicode no-break space
+    private static final String SPACER_SPAN_TEXT = "\u00a0";
+
+    private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
+    private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
+    private static final String FIRST_STRONG_ISOLATE = "\u2068";
+    private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
+
+    private void glueIconAndLabel(int layoutDirection) {
+        final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.d(TAG, "glueIconAndLabel: "
+                    + "icon = " + mIconToGlue + ", "
+                    + "iconSize = " + mIconSize + "px, "
+                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
+                    + "labelToGlue.length = " + mLabelToGlue.length() + ", "
+                    + "rtlLayout = " + rtlLayout);
+        }
+
+        logIfTextDirectionNotFirstStrong();
+
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+        // The text direction of the label might not match the layout direction of the button, so
+        // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
+        // layout direction. This puts the icon, padding, and label in the right order.
+        builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);
+
+        appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
+        appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));
+
+        // If the text and layout directions are different, we would end up with the *label* in the
+        // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
+        // automatic text direction heuristic that Android uses by default.
+        builder.append(FIRST_STRONG_ISOLATE);
+
+        appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));
+
+        builder.append(POP_DIRECTIONAL_ISOLATE);
+        builder.append(POP_DIRECTIONAL_ISOLATE);
+
+        setText(builder);
+    }
+
+    private void logIfTextDirectionNotFirstStrong() {
+        if (!isTextDirectionResolved()) {
+            Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
+                    + "letting View assume FIRST STRONG");
+        }
+        final int textDirection = getTextDirection();
+        if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
+            Log.w(TAG, "glueIconAndLabel: "
+                    + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
+                    + "but found " + textDirection + "; "
+                    + "will use a FIRST STRONG ISOLATE regardless");
+        }
+    }
+
+    private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
+        final int spanStart = builder.length();
+        builder.append(text);
+        final int spanEnd = builder.length();
+        builder.setSpan(span, spanStart, spanEnd, 0);
+    }
+
+    /**
      * Sets whether this view is a priority over its peers (which affects width).
      * Specifically, this is used by {@link NotificationActionListLayout} to give this view width
      * priority ahead of user-defined buttons when allocating horizontal space.
@@ -123,4 +389,104 @@
     public boolean isPriority() {
         return mPriority;
     }
+
+    private static class SpacerSpan extends ReplacementSpan {
+        private int mWidth;
+
+        SpacerSpan(int width) {
+            mWidth = width;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "width = " + mWidth + "px");
+            }
+        }
+
+
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+                           @Nullable Paint.FontMetricsInt fontMetrics) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "getSize returning " + mWidth + "px");
+            }
+
+            return mWidth;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+                         float x, int top, int y, int bottom, @NonNull Paint paint) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "drawing nothing");
+            }
+
+            // Draw nothing, it's a spacer.
+        }
+
+        private static final String TAG = "SpacerSpan";
+    }
+
+    private static class CenterBesideImageSpan extends MetricAffectingSpan {
+        private int mImageHeight;
+
+        private boolean mMeasured;
+        private int mBaselineShiftOffset;
+
+        CenterBesideImageSpan(int imageHeight) {
+            mImageHeight = imageHeight;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "imageHeight = " + mImageHeight + "px");
+            }
+        }
+
+        @Override
+        public void updateMeasureState(@NonNull TextPaint textPaint) {
+            final int textHeight = (int) -textPaint.ascent();
+
+            /*
+             * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
+             * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
+             */
+            if (textHeight < mImageHeight) {
+                mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
+            } else {
+                mBaselineShiftOffset = 0;
+            }
+
+            mMeasured = true;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "updateMeasureState: "
+                        + "imageHeight = " + mImageHeight + "px, "
+                        + "textHeight = " + textHeight + "px, "
+                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+            }
+
+            textPaint.baselineShift += mBaselineShiftOffset;
+        }
+
+        @Override
+        public void updateDrawState(TextPaint textPaint) {
+            if (textPaint == null) {
+                Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
+                return;
+            }
+
+            if (!mMeasured) {
+                Log.e(TAG, "updateDrawState: called without measure; doing nothing");
+                return;
+            }
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "updateDrawState: "
+                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+            }
+
+            textPaint.baselineShift += mBaselineShiftOffset;
+        }
+
+        private static final String TAG = "CenterBesideImageSpan";
+    }
+
+    private static final String TAG = "EmphasizedNotificationButton";
 }
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 627e877..e591327 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -171,11 +171,11 @@
      * Register a LockSettingsStateListener
      * @param listener The listener to be registered
      */
-    public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener);
+    public abstract void registerLockSettingsStateListener(LockSettingsStateListener listener);
 
     /**
      * Unregister a LockSettingsStateListener
      * @param listener The listener to be unregistered
      */
-    public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener);
+    public abstract void unregisterLockSettingsStateListener(LockSettingsStateListener listener);
 }
diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/LockSettingsStateListener.java
similarity index 91%
rename from core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
rename to core/java/com/android/internal/widget/LockSettingsStateListener.java
index 25e3003..869e676 100644
--- a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
+++ b/core/java/com/android/internal/widget/LockSettingsStateListener.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,7 +21,7 @@
  * state of primary authentication (i.e. PIN/pattern/password).
  * @hide
  */
-oneway interface ILockSettingsStateListener {
+public interface LockSettingsStateListener {
     /**
      * Defines behavior in response to a successful authentication
      * @param userId The user Id for the requested authentication
@@ -33,4 +33,4 @@
      * @param userId The user Id for the requested authentication
      */
     void onAuthenticationFailed(int userId);
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 30e4099..f8f1049 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -55,7 +55,9 @@
  * A message of a {@link MessagingLayout}.
  */
 @RemoteViews.RemoteView
-public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+public class MessagingGroup extends NotificationOptimizedLinearLayout implements
+        MessagingLinearLayout.MessagingChild {
+
     private static final MessagingPool<MessagingGroup> sInstancePool =
             new MessagingPool<>(10);
 
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index a7a69c9..69d2544 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.widget;
 
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+
 import android.annotation.DimenRes;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -41,13 +45,13 @@
  */
 @RemoteViews.RemoteView
 public class NotificationActionListLayout extends LinearLayout {
-
     private final int mGravity;
     private int mTotalWidth = 0;
     private int mExtraStartPadding = 0;
     private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
     private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
     private boolean mEmphasizedMode;
+    private boolean mEvenlyDividedMode;
     private int mDefaultPaddingBottom;
     private int mDefaultPaddingTop;
     private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@
         }
     }
 
+    private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
+        final int numChildren = getChildCount();
+        int childMarginSum = 0;
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+                childMarginSum += lp.leftMargin + lp.rightMargin;
+            }
+        }
+
+        final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
+        final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
+        final int childWidthMeasureSpec =
+                MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "measuring evenly divided width: "
+                    + "numChildren = " + numChildren + ", "
+                    + "innerWidth = " + innerWidth + "px, "
+                    + "childMarginSum = " + childMarginSum + "px, "
+                    + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
+                    + "childWidth = " + childWidth + "px, "
+                    + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
+        }
+
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                child.measure(childWidthMeasureSpec, heightMeasureSpec);
+            }
+        }
+
+        return innerWidth;
+    }
+
     private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
             boolean collapsePriorityActions) {
         final int numChildren = getChildCount();
@@ -208,11 +248,16 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         countAndRebuildMeasureOrder();
         final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
-        int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
-                false /* collapsePriorityButtons */);
-        if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+        int usedWidth;
+        if (mEvenlyDividedMode) {
+            usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
+        } else {
             usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
-                    true /* collapsePriorityButtons */);
+                    false /* collapsePriorityButtons */);
+            if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+                usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+                        true /* collapsePriorityButtons */);
+            }
         }
 
         mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
@@ -352,6 +397,38 @@
     }
 
     /**
+     * Sets whether the available width should be distributed evenly among the action buttons.
+     *
+     * When enabled, the available width (after subtracting this layout's padding and all of the
+     * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
+     * to that exact width, even if it is less <em>or more</em> width than they need.
+     *
+     * When disabled, the available width is allocated as buttons need; if that exceeds the
+     * available width, priority buttons are collapsed to just their icon to save space.
+     *
+     * @param evenlyDividedMode whether to enable evenly divided mode
+     */
+    @RemotableViewMethod
+    public void setEvenlyDividedMode(boolean evenlyDividedMode) {
+        if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+                    + "leaving evenly divided mode disabled");
+            return;
+        }
+
+        if (evenlyDividedMode == mEvenlyDividedMode) {
+            return;
+        }
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
+                    + "requesting layout");
+        }
+        mEvenlyDividedMode = evenlyDividedMode;
+        requestLayout();
+    }
+
+    /**
      * Set whether the list is in a mode where some actions are emphasized. This will trigger an
      * equal measuring where all actions are full height and change a few parameters like
      * the padding.
@@ -410,4 +487,5 @@
         }
     }
 
+    private static final String TAG = "NotificationActionListLayout";
 }
diff --git a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
new file mode 100644
index 0000000..b5e9b8f
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static android.widget.flags.Flags.notifLinearlayoutOptimized;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This LinearLayout customizes the measurement behavior of LinearLayout for Notification layouts.
+ * When there is exactly
+ * one child View with <code>layout_weight</code>. onMeasure methods of this LinearLayout will:
+ * 1. Measure all other children.
+ * 2. Calculate the remaining space for the View with <code>layout_weight</code>
+ * 3. Measure the weighted View using the calculated remaining width or height (based on
+ * Orientation).
+ * This ensures that the weighted View fills the remaining space in LinearLayout with only single
+ * measure.
+ *
+ * **Assumptions:**
+ * - There is *exactly one* child view with non-zero <code>layout_weight</code>.
+ * - Other views should not have weight.
+ * - LinearLayout doesn't have <code>weightSum</code>.
+ * - Horizontal LinearLayout's width should be measured EXACTLY.
+ * - Horizontal LinearLayout shouldn't need baseLineAlignment.
+ * - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationOptimizedLinearLayout extends LinearLayout {
+    private static final boolean DEBUG_LAYOUT = false;
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+    private static final String TAG = "NotifOptimizedLinearLayout";
+
+    private boolean mShouldUseOptimizedLayout = false;
+
+    public NotificationOptimizedLinearLayout(Context context) {
+        super(context);
+    }
+
+    public NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public NotificationOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final View weightedChildView = getSingleWeightedChild();
+        mShouldUseOptimizedLayout =
+                isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null
+                        && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec);
+
+        if (mShouldUseOptimizedLayout) {
+            onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    private boolean isUseOptimizedLinearLayoutFlagEnabled() {
+        final boolean enabled = notifLinearlayoutOptimized();
+        if (!enabled) {
+            logSkipOptimizedOnMeasure("enableNotifLinearlayoutOptimized flag is off.");
+        }
+        return enabled;
+    }
+
+    /**
+     * Checks if optimizations can be safely applied to this LinearLayout during layout
+     * calculations. Optimizations might be disabled in the following cases:
+     *
+     * **weightSum**: When LinearLayout has weightSum
+     * ** MATCH_PARENT children in non EXACT dimension**
+     * **Horizontal LinearLayout with non-EXACT width**
+     * **Baseline Alignment:**  If views need to align their baselines in Horizontal LinearLayout
+     *
+     * @param widthMeasureSpec  The width measurement specification.
+     * @param heightMeasureSpec The height measurement specification.
+     * @return `true` if optimization is possible, `false` otherwise.
+     */
+    private boolean isLinearLayoutUsable(int widthMeasureSpec, int heightMeasureSpec) {
+        final boolean hasWeightSum = getWeightSum() > 0.0f;
+        if (hasWeightSum) {
+            logSkipOptimizedOnMeasure("Has weightSum.");
+            return false;
+        }
+
+        if (requiresMatchParentRemeasureForVerticalLinearLayout(widthMeasureSpec)) {
+            logSkipOptimizedOnMeasure(
+                    "Vertical LinearLayout requires children width MATCH_PARENT remeasure ");
+            return false;
+        }
+
+        final boolean isHorizontal = getOrientation() == HORIZONTAL;
+        if (isHorizontal && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
+            logSkipOptimizedOnMeasure("Horizontal LinearLayout's width should be "
+                    + "measured EXACTLY");
+            return false;
+        }
+
+        if (requiresBaselineAlignmentForHorizontalLinearLayout()) {
+            logSkipOptimizedOnMeasure("Need to apply baseline.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @return if the vertical linearlayout requires match_parent children remeasure
+     */
+    private boolean requiresMatchParentRemeasureForVerticalLinearLayout(int widthMeasureSpec) {
+        // HORIZONTAL measuring is handled by LinearLayout. That's why we don't need to check it
+        // here.
+        if (getOrientation() == HORIZONTAL) {
+            return false;
+        }
+
+        // When the width is not EXACT, children with MATCH_PARENT width need to be double measured.
+        // This needs to be handled in LinearLayout because NotificationOptimizedLinearLayout
+        final boolean nonExactWidth =
+                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY;
+        final List<View> activeChildren = getActiveChildren();
+        for (int i = 0; i < activeChildren.size(); i++) {
+            final View child = activeChildren.get(i);
+            final ViewGroup.LayoutParams lp = child.getLayoutParams();
+            if (nonExactWidth && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return if this layout needs to apply baseLineAlignment.
+     */
+    private boolean requiresBaselineAlignmentForHorizontalLinearLayout() {
+        // baseLineAlignment is not important for Vertical LinearLayout.
+        if (getOrientation() == VERTICAL) {
+            return false;
+        }
+        // Early return, if it is already disabled
+        if (!isBaselineAligned()) {
+            return false;
+        }
+
+        final List<View> activeChildren = getActiveChildren();
+        final int minorGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+
+        for (int i = 0; i < activeChildren.size(); i++) {
+            final View child = activeChildren.get(i);
+            if (child.getLayoutParams() instanceof LayoutParams) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                int childBaseline = -1;
+
+                if (lp.height != LayoutParams.MATCH_PARENT) {
+                    childBaseline = child.getBaseline();
+                }
+                if (childBaseline == -1) {
+                    // This child doesn't have a baseline.
+                    continue;
+                }
+                int gravity = lp.gravity;
+                if (gravity < 0) {
+                    gravity = minorGravity;
+                }
+
+                final int result = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+                if (result == Gravity.TOP || result == Gravity.BOTTOM) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Finds the single child view within this layout that has a non-zero weight assigned to its
+     * LayoutParams.
+     *
+     * @return The weighted child view, or null if multiple weighted children exist or no weighted
+     * children are found.
+     */
+    @Nullable
+    private View getSingleWeightedChild() {
+        final boolean isVertical = getOrientation() == VERTICAL;
+        final List<View> activeChildren = getActiveChildren();
+        View singleWeightedChild = null;
+        for (int i = 0; i < activeChildren.size(); i++) {
+            final View child = activeChildren.get(i);
+            if (child.getLayoutParams() instanceof LayoutParams) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if ((!isVertical && lp.width == ViewGroup.LayoutParams.MATCH_PARENT)
+                        || (isVertical && lp.height == ViewGroup.LayoutParams.MATCH_PARENT)) {
+                    logSkipOptimizedOnMeasure(
+                            "There is a match parent child in the related orientation.");
+                    return null;
+                }
+                if (lp.weight != 0) {
+                    if (singleWeightedChild == null) {
+                        singleWeightedChild = child;
+                    } else {
+                        logSkipOptimizedOnMeasure("There is more than one weighted child.");
+                        return null;
+                    }
+                }
+            }
+        }
+        if (singleWeightedChild == null) {
+            logSkipOptimizedOnMeasure("There is no weighted child in this layout.");
+        } else {
+            final LayoutParams lp = (LayoutParams) singleWeightedChild.getLayoutParams();
+            boolean isHeightWrapContentOrZero =
+                    lp.height == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == 0;
+            boolean isWidthWrapContentOrZero =
+                    lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.width == 0;
+            if ((isVertical && !isHeightWrapContentOrZero)
+                    || (!isVertical && !isWidthWrapContentOrZero)) {
+                logSkipOptimizedOnMeasure(
+                        "Single weighted child should be either WRAP_CONTENT or 0"
+                                + " in the related orientation");
+                singleWeightedChild = null;
+            }
+        }
+
+        return singleWeightedChild;
+    }
+
+    /**
+     * Optimized measurement for the single weighted child in this LinearLayout.
+     * Measures other children, calculates remaining space, then measures the weighted
+     * child using the remaining width (or height).
+     *
+     * Note: Horizontal LinearLayout doesn't need to apply baseline in optimized case @see
+     * {@link #requiresBaselineAlignmentForHorizontalLinearLayout}.
+     *
+     * @param weightedChildView The weighted child view(with `layout_weight!=0`)
+     * @param widthMeasureSpec  The width MeasureSpec to use for measurement
+     * @param heightMeasureSpec The height MeasureSpec to use for measurement.
+     */
+    private void onMeasureOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
+            int heightMeasureSpec) {
+        try {
+            if (TRACE_ONMEASURE) {
+                Trace.beginSection("NotifOptimizedLinearLayout#onMeasure");
+            }
+
+            if (getOrientation() == LinearLayout.HORIZONTAL) {
+                final ViewGroup.LayoutParams lp = weightedChildView.getLayoutParams();
+                final int childWidth = lp.width;
+                final boolean isBaselineAligned = isBaselineAligned();
+                // It should be marked 0 so that it use excessSpace in LinearLayout's onMeasure
+                lp.width = 0;
+
+                // It doesn't need to apply baseline. So disable it.
+                setBaselineAligned(false);
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+                // restore values.
+                lp.width = childWidth;
+                setBaselineAligned(isBaselineAligned);
+            } else {
+                measureVerticalOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
+            }
+        } finally {
+            if (TRACE_ONMEASURE) {
+                trackShouldUseOptimizedLayout();
+                Trace.endSection();
+            }
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (mShouldUseOptimizedLayout) {
+            onLayoutOptimized(changed, l, t, r, b);
+        } else {
+            super.onLayout(changed, l, t, r, b);
+        }
+    }
+
+    private void onLayoutOptimized(boolean changed, int l, int t, int r, int b) {
+        if (getOrientation() == LinearLayout.HORIZONTAL) {
+            super.onLayout(changed, l, t, r, b);
+        } else {
+            layoutVerticalOptimized(l, t, r, b);
+        }
+    }
+
+    /**
+     * Optimized measurement for the single weighted child in this LinearLayout.
+     * Measures other children, calculates remaining space, then measures the weighted
+     * child using the exact remaining height.
+     *
+     * @param weightedChildView The weighted child view(with `layout_weight=1`
+     * @param widthMeasureSpec  The width MeasureSpec to use for measurement
+     * @param heightMeasureSpec The height MeasureSpec to use for measurement.
+     */
+    private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
+            int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int maxWidth = 0;
+        int usedHeight = 0;
+        final List<View> activeChildren = getActiveChildren();
+        final int activeChildCount = activeChildren.size();
+
+        final boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0)
+                == weightedChildView;
+
+        final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get(
+                activeChildCount - 1) == weightedChildView;
+
+        final int horizontalPaddings = getPaddingLeft() + getPaddingRight();
+
+        // 1. Measure other child views.
+        for (int i = 0; i < activeChildCount; i++) {
+            final View child = activeChildren.get(i);
+            if (child == weightedChildView) {
+                continue;
+            }
+            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+            int requiredVerticalPadding = lp.topMargin + lp.bottomMargin;
+            if (!isContentFirstItem && i == 0) {
+                requiredVerticalPadding += getPaddingTop();
+            }
+            if (!isContentLastItem && i == activeChildCount - 1) {
+                requiredVerticalPadding += getPaddingBottom();
+            }
+
+            child.measure(ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+                            horizontalPaddings + lp.leftMargin + lp.rightMargin,
+                            child.getLayoutParams().width),
+                    ViewGroup.getChildMeasureSpec(heightMeasureSpec, requiredVerticalPadding,
+                            lp.height));
+            maxWidth = Math.max(maxWidth,
+                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+            usedHeight += child.getMeasuredHeight() + requiredVerticalPadding;
+        }
+
+        // measure content
+        final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams();
+
+        int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin;
+        if (isContentFirstItem) {
+            usedSpace += getPaddingTop();
+        }
+        if (isContentLastItem) {
+            usedSpace += getPaddingBottom();
+        }
+
+        final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+        final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+                horizontalPaddings + lp.leftMargin + lp.rightMargin, lp.width);
+
+        // 2. Calculate remaining height for weightedChildView.
+        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST);
+
+        // 3. Measure weightedChildView with the remaining remaining space.
+        weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        maxWidth = Math.max(maxWidth,
+                weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+
+        final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight();
+
+        final int measuredWidth;
+        if (widthMode == MeasureSpec.EXACTLY) {
+            measuredWidth = availableWidth;
+        } else {
+            measuredWidth = maxWidth + getPaddingStart() + getPaddingEnd();
+        }
+
+        final int measuredHeight;
+        if (heightMode == MeasureSpec.EXACTLY) {
+            measuredHeight = availableHeight;
+        } else {
+            measuredHeight = totalUsedHeight;
+        }
+
+        // 4. Set the container size
+        setMeasuredDimension(
+                resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
+                        widthMeasureSpec),
+                Math.max(getSuggestedMinimumHeight(), measuredHeight));
+    }
+
+    @NonNull
+    private List<View> getActiveChildren() {
+        final int childCount = getChildCount();
+        final List<View> activeChildren = new ArrayList<>();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child == null || child.getVisibility() == View.GONE) {
+                continue;
+            }
+            activeChildren.add(child);
+        }
+        return activeChildren;
+    }
+
+    //region LinearLayout copy methods
+
+    /**
+     * layoutVerticalOptimized is a version of LinearLayout's layoutVertical method that
+     * excludes
+     * TableRow-related functionalities.
+     *
+     * @see LinearLayout#onLayout(boolean, int, int, int, int)
+     */
+    private void layoutVerticalOptimized(int left, int top, int right,
+            int bottom) {
+        final int paddingLeft = mPaddingLeft;
+        final int mTotalLength = getMeasuredHeight();
+        int childTop;
+        int childLeft;
+
+        // Where right end of child should go
+        final int width = right - left;
+        int childRight = width - mPaddingRight;
+
+        // Space available for child
+        int childSpace = width - paddingLeft - mPaddingRight;
+
+        final int count = getChildCount();
+
+        final int majorGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+        final int minorGravity = getGravity() & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+        switch (majorGravity) {
+            case Gravity.BOTTOM:
+                // mTotalLength contains the padding already
+                childTop = mPaddingTop + bottom - top - mTotalLength;
+                break;
+
+            // mTotalLength contains the padding already
+            case Gravity.CENTER_VERTICAL:
+                childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
+                break;
+
+            case Gravity.TOP:
+            default:
+                childTop = mPaddingTop;
+                break;
+        }
+        final int dividerHeight = getDividerHeight();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+
+                final LinearLayout.LayoutParams lp =
+                        (LinearLayout.LayoutParams) child.getLayoutParams();
+
+                int gravity = lp.gravity;
+                if (gravity < 0) {
+                    gravity = minorGravity;
+                }
+                final int layoutDirection = getLayoutDirection();
+                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity,
+                        layoutDirection);
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft =
+                                paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin
+                                        - lp.rightMargin;
+                        break;
+
+                    case Gravity.RIGHT:
+                        childLeft = childRight - childWidth - lp.rightMargin;
+                        break;
+
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = paddingLeft + lp.leftMargin;
+                        break;
+                }
+
+                if (hasDividerBeforeChildAt(i)) {
+                    childTop += dividerHeight;
+                }
+
+                childTop += lp.topMargin;
+                child.layout(childLeft, childTop, childLeft + childWidth,
+                        childTop + childHeight);
+                childTop += childHeight + lp.bottomMargin;
+
+            }
+        }
+    }
+
+    /**
+     * Used in laying out views vertically.
+     *
+     * @see #layoutVerticalOptimized
+     * @see LinearLayout#onLayout(boolean, int, int, int, int)
+     */
+    private int getDividerHeight() {
+        final Drawable dividerDrawable = getDividerDrawable();
+        if (dividerDrawable == null) {
+            return 0;
+        } else {
+            return dividerDrawable.getIntrinsicHeight();
+        }
+    }
+    //endregion
+
+    //region Logging&Tracing
+    private void trackShouldUseOptimizedLayout() {
+        if (TRACE_ONMEASURE) {
+            Trace.setCounter("NotifOptimizedLinearLayout#shouldUseOptimizedLayout",
+                    mShouldUseOptimizedLayout ? 1 : 0);
+        }
+    }
+
+    private void logSkipOptimizedOnMeasure(String reason) {
+        if (DEBUG_LAYOUT) {
+            final StringBuilder logMessage = new StringBuilder();
+            int layoutId = getId();
+            if (layoutId != NO_ID) {
+                final Resources resources = getResources();
+                if (resources != null) {
+                    logMessage.append("[");
+                    logMessage.append(resources.getResourceName(layoutId));
+                    logMessage.append("] ");
+                }
+            }
+            logMessage.append("Going to skip onMeasureOptimized reason:");
+            logMessage.append(reason);
+
+            Log.d(TAG, logMessage.toString());
+        }
+    }
+    //endregion
+}
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
index 7b154a5..bddad94 100644
--- a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -30,7 +30,7 @@
  * MessagingLayouts where groups need to be able to snap it's height to.
  */
 @RemoteViews.RemoteView
-public class RemeasuringLinearLayout extends LinearLayout {
+public class RemeasuringLinearLayout extends NotificationOptimizedLinearLayout {
 
     private ArrayList<View> mMatchParentViews = new ArrayList<>();
 
@@ -79,7 +79,7 @@
             int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
             for (View child : mMatchParentViews) {
                 child.measure(getChildMeasureSpec(
-                        widthMeasureSpec, getPaddingStart() + getPaddingEnd(),
+                                widthMeasureSpec, getPaddingStart() + getPaddingEnd(),
                         child.getLayoutParams().width),
                         exactHeightSpec);
             }
@@ -87,4 +87,5 @@
         mMatchParentViews.clear();
         setMeasuredDimension(getMeasuredWidth(), height);
     }
+
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS
new file mode 100644
index 0000000..54facab
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/OWNERS
@@ -0,0 +1,8 @@
+nicolasroard@google.com
+hoford@google.com
+jnichol@google.com
+sihua@google.com
+sunnygoyal@google.com
+oscarad@google.com
+pinyaoting@google.com
+zakcohen@google.com
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
similarity index 60%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
index a368de2..ce8ca0d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,10 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.internal.widget.remotecompose.core;
 
-package com.android.credentialmanager.ui.model
+import java.util.List;
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+/**
+ * Interface for the companion operations
+ */
+public interface CompanionOperation {
+    void read(WireBuffer buffer, List<Operation> operations);
+
+    // Debugging / Documentation utility functions
+    String name();
+    int id();
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
new file mode 100644
index 0000000..0e4c743
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -0,0 +1,494 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents a platform independent RemoteCompose document,
+ * containing RemoteCompose operations + state
+ */
+public class CoreDocument {
+
+    ArrayList<Operation> mOperations;
+    RemoteComposeState mRemoteComposeState = new RemoteComposeState();
+
+    // Semantic version of the document
+    Version mVersion = new Version(0, 1, 0);
+
+    String mContentDescription; // text description of the document (used for accessibility)
+
+    long mRequiredCapabilities = 0L; // bitmask indicating needed capabilities of the player(unused)
+    int mWidth = 0; // horizontal dimension of the document in pixels
+    int mHeight = 0; // vertical dimension of the document in pixels
+
+    int mContentScroll = RootContentBehavior.NONE;
+    int mContentSizing = RootContentBehavior.NONE;
+    int mContentMode = RootContentBehavior.NONE;
+
+    int mContentAlignment = RootContentBehavior.ALIGNMENT_CENTER;
+
+    RemoteComposeBuffer mBuffer = new RemoteComposeBuffer(mRemoteComposeState);
+
+    public String getContentDescription() {
+        return mContentDescription;
+    }
+
+    public void setContentDescription(String contentDescription) {
+        this.mContentDescription = contentDescription;
+    }
+
+    public long getRequiredCapabilities() {
+        return mRequiredCapabilities;
+    }
+
+    public void setRequiredCapabilities(long requiredCapabilities) {
+        this.mRequiredCapabilities = requiredCapabilities;
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public void setWidth(int width) {
+        this.mWidth = width;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public void setHeight(int height) {
+        this.mHeight = height;
+    }
+
+    public RemoteComposeBuffer getBuffer() {
+        return mBuffer;
+    }
+
+    public void setBuffer(RemoteComposeBuffer buffer) {
+        this.mBuffer = buffer;
+    }
+
+    public RemoteComposeState getRemoteComposeState() {
+        return mRemoteComposeState;
+    }
+
+    public void setRemoteComposeState(RemoteComposeState remoteComposeState) {
+        this.mRemoteComposeState = remoteComposeState;
+    }
+
+    public int getContentScroll() {
+        return mContentScroll;
+    }
+
+    public int getContentSizing() {
+        return mContentSizing;
+    }
+
+    public int getContentMode() {
+        return mContentMode;
+    }
+
+    /**
+     * Sets the way the player handles the content
+     *
+     * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+     * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+     * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+     * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+     *             the LAYOUT modes are:
+     *             - LAYOUT_MATCH_PARENT
+     *             - LAYOUT_WRAP_CONTENT
+     *             or adding an horizontal mode and a vertical mode:
+     *             - LAYOUT_HORIZONTAL_MATCH_PARENT
+     *             - LAYOUT_HORIZONTAL_WRAP_CONTENT
+     *             - LAYOUT_HORIZONTAL_FIXED
+     *             - LAYOUT_VERTICAL_MATCH_PARENT
+     *             - LAYOUT_VERTICAL_WRAP_CONTENT
+     *             - LAYOUT_VERTICAL_FIXED
+     *             The LAYOUT_*_FIXED modes will use the intrinsic document size
+     */
+    public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+        this.mContentScroll = scroll;
+        this.mContentAlignment = alignment;
+        this.mContentSizing = sizing;
+        this.mContentMode = mode;
+    }
+
+    /**
+     * Given dimensions w x h of where to paint the content, returns the corresponding scale factor
+     * according to the contentSizing information
+     *
+     * @param w horizontal dimension of the rendering area
+     * @param h vertical dimension of the rendering area
+     * @param scaleOutput will contain the computed scale factor
+     */
+    public void computeScale(float w, float h, float[] scaleOutput) {
+        float contentScaleX = 1f;
+        float contentScaleY = 1f;
+        if (mContentSizing == RootContentBehavior.SIZING_SCALE) {
+            // we need to add canvas transforms ops here
+            switch (mContentMode) {
+                case RootContentBehavior.SCALE_INSIDE: {
+                    float scaleX = w / mWidth;
+                    float scaleY = h / mHeight;
+                    float scale = Math.min(1f, Math.min(scaleX, scaleY));
+                    contentScaleX = scale;
+                    contentScaleY = scale;
+                } break;
+                case RootContentBehavior.SCALE_FIT: {
+                    float scaleX = w / mWidth;
+                    float scaleY = h / mHeight;
+                    float scale = Math.min(scaleX, scaleY);
+                    contentScaleX = scale;
+                    contentScaleY = scale;
+                } break;
+                case RootContentBehavior.SCALE_FILL_WIDTH: {
+                    float scale = w / mWidth;
+                    contentScaleX = scale;
+                    contentScaleY = scale;
+                } break;
+                case RootContentBehavior.SCALE_FILL_HEIGHT: {
+                    float scale = h / mHeight;
+                    contentScaleX = scale;
+                    contentScaleY = scale;
+                } break;
+                case RootContentBehavior.SCALE_CROP: {
+                    float scaleX = w / mWidth;
+                    float scaleY = h / mHeight;
+                    float scale = Math.max(scaleX, scaleY);
+                    contentScaleX = scale;
+                    contentScaleY = scale;
+                } break;
+                case RootContentBehavior.SCALE_FILL_BOUNDS: {
+                    float scaleX = w / mWidth;
+                    float scaleY = h / mHeight;
+                    contentScaleX = scaleX;
+                    contentScaleY = scaleY;
+                } break;
+                default:
+                    // nothing
+            }
+        }
+        scaleOutput[0] = contentScaleX;
+        scaleOutput[1] = contentScaleY;
+    }
+
+    /**
+     * Given dimensions w x h of where to paint the content, returns the corresponding translation
+     * according to the contentAlignment information
+     *
+     * @param w horizontal dimension of the rendering area
+     * @param h vertical dimension of the rendering area
+     * @param contentScaleX the horizontal scale we are going to use for the content
+     * @param contentScaleY the vertical scale we are going to use for the content
+     * @param translateOutput will contain the computed translation
+     */
+    private void computeTranslate(float w, float h, float contentScaleX, float contentScaleY,
+                                  float[] translateOutput) {
+        int horizontalContentAlignment = mContentAlignment & 0xF0;
+        int verticalContentAlignment = mContentAlignment & 0xF;
+        float translateX = 0f;
+        float translateY = 0f;
+        float contentWidth = mWidth * contentScaleX;
+        float contentHeight = mHeight * contentScaleY;
+
+        switch (horizontalContentAlignment) {
+            case RootContentBehavior.ALIGNMENT_START: {
+                // nothing
+            } break;
+            case RootContentBehavior.ALIGNMENT_HORIZONTAL_CENTER: {
+                translateX = (w - contentWidth) / 2f;
+            } break;
+            case RootContentBehavior.ALIGNMENT_END: {
+                translateX = w - contentWidth;
+            } break;
+            default:
+                // nothing (same as alignment_start)
+        }
+        switch (verticalContentAlignment) {
+            case RootContentBehavior.ALIGNMENT_TOP: {
+                // nothing
+            } break;
+            case RootContentBehavior.ALIGNMENT_VERTICAL_CENTER: {
+                translateY = (h - contentHeight) / 2f;
+            } break;
+            case RootContentBehavior.ALIGNMENT_BOTTOM: {
+                translateY = h - contentHeight;
+            } break;
+            default:
+                // nothing (same as alignment_top)
+        }
+
+        translateOutput[0] = translateX;
+        translateOutput[1] = translateY;
+    }
+
+    public Set<ClickAreaRepresentation> getClickAreas() {
+        return mClickAreas;
+    }
+
+    public interface ClickCallbacks {
+        void click(int id, String metadata);
+    }
+
+    HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
+    HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
+
+    static class Version {
+        public final int major;
+        public final int minor;
+        public final int patchLevel;
+
+        Version(int major, int minor, int patchLevel) {
+            this.major = major;
+            this.minor = minor;
+            this.patchLevel = patchLevel;
+        }
+    }
+
+    public static class ClickAreaRepresentation {
+        int mId;
+        String mContentDescription;
+        float mLeft;
+        float mTop;
+        float mRight;
+        float mBottom;
+        String mMetadata;
+
+        public ClickAreaRepresentation(int id,
+                                       String contentDescription,
+                                       float left,
+                                       float top,
+                                       float right,
+                                       float bottom,
+                                       String metadata) {
+            this.mId = id;
+            this.mContentDescription = contentDescription;
+            this.mLeft = left;
+            this.mTop = top;
+            this.mRight = right;
+            this.mBottom = bottom;
+            this.mMetadata = metadata;
+        }
+
+        public boolean contains(float x, float y)  {
+            return x >= mLeft && x < mRight
+                    && y >= mTop && y < mBottom;
+        }
+
+        public float getLeft() {
+            return mLeft;
+        }
+
+        public float getTop() {
+            return mTop;
+        }
+
+        public float width() {
+            return Math.max(0, mRight - mLeft);
+        }
+
+        public float height() {
+            return Math.max(0, mBottom - mTop);
+        }
+
+        public int getId() {
+            return mId;
+        }
+
+        public String getContentDescription() {
+            return mContentDescription;
+        }
+
+        public String getMetadata() {
+            return mMetadata;
+        }
+    }
+
+    /**
+     * Load operations from the given buffer
+     */
+    public void initFromBuffer(RemoteComposeBuffer buffer) {
+        mOperations = new ArrayList<Operation>();
+        buffer.inflateFromBuffer(mOperations);
+    }
+
+    /**
+     * Called when an initialization is needed, allowing the document to eg load
+     * resources / cache them.
+     */
+    public void initializeContext(RemoteContext context) {
+        mRemoteComposeState.reset();
+        mClickAreas.clear();
+
+        context.mDocument = this;
+        context.mRemoteComposeState = mRemoteComposeState;
+
+        // mark context to be in DATA mode, which will skip the painting ops.
+        context.mMode = RemoteContext.ContextMode.DATA;
+        for (Operation op: mOperations) {
+            op.apply(context);
+        }
+        context.mMode = RemoteContext.ContextMode.UNSET;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Document infos
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns true if the document can be displayed given this version of the player
+     *
+     * @param majorVersion the max major version supported by the player
+     * @param minorVersion the max minor version supported by the player
+     * @param capabilities a bitmask of capabilities the player supports (unused for now)
+     */
+    public boolean canBeDisplayed(int majorVersion, int minorVersion, long capabilities) {
+        return mVersion.major <= majorVersion && mVersion.minor <= minorVersion;
+    }
+
+    /**
+     * Set the document version, following semantic versioning.
+     *
+     * @param majorVersion major version number, increased upon changes breaking the compatibility
+     * @param minorVersion minor version number, increased when adding new features
+     * @param patch        patch level, increased upon bugfixes
+     */
+    void  setVersion(int majorVersion, int minorVersion, int patch) {
+        mVersion = new Version(majorVersion, minorVersion, patch);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Click handling
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Add a click area to the document, in root coordinates. We are not doing any specific sorting
+     * through the declared areas on click detections, which means that the first one containing
+     * the click coordinates will be the one reported; the order of addition of those click areas
+     * is therefore meaningful.
+     *
+     * @param id       the id of the area, which will be reported on click
+     * @param contentDescription the content description (used for accessibility)
+     * @param left     the left coordinate of the click area (in pixels)
+     * @param top      the top coordinate of the click area (in pixels)
+     * @param right    the right coordinate of the click area (in pixels)
+     * @param bottom   the bottom coordinate of the click area (in pixels)
+     * @param metadata arbitrary metadata associated with the are, also reported on click
+     */
+    public void addClickArea(int id, String contentDescription,
+                             float left, float top, float right, float bottom, String metadata) {
+        mClickAreas.add(new ClickAreaRepresentation(id,
+                contentDescription, left, top, right, bottom, metadata));
+    }
+
+    /**
+     * Add a click listener. This will get called when a click is detected on the document
+     *
+     * @param callback called when a click area has been hit, passing the click are id and metadata.
+     */
+    public void addClickListener(ClickCallbacks callback) {
+        mClickListeners.add(callback);
+    }
+
+    /**
+     * Passing a click event to the document. This will possibly result in calling the click
+     * listeners.
+     */
+    public void onClick(float x, float y) {
+        for (ClickAreaRepresentation clickArea: mClickAreas) {
+            if (clickArea.contains(x, y)) {
+                warnClickListeners(clickArea);
+            }
+        }
+    }
+
+    /**
+     * Programmatically trigger the click response for the given id
+     *
+     * @param id the click area id
+     */
+    public void performClick(int id) {
+        for (ClickAreaRepresentation clickArea: mClickAreas) {
+            if (clickArea.mId == id) {
+                warnClickListeners(clickArea);
+            }
+        }
+    }
+
+    /**
+     * Warn click listeners when a click area is activated
+     */
+    private void warnClickListeners(ClickAreaRepresentation clickArea) {
+        for (ClickCallbacks listener: mClickListeners) {
+            listener.click(clickArea.mId, clickArea.mMetadata);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Painting
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    private final float[] mScaleOutput = new float[2];
+    private final float[] mTranslateOutput = new float[2];
+
+    /**
+     * Paint the document
+     *
+     * @param context the provided PaintContext
+     * @param theme   the theme we want to use for this document.
+     */
+    public void paint(RemoteContext context, int theme) {
+        context.mMode = RemoteContext.ContextMode.PAINT;
+
+        // current theme starts as UNSPECIFIED, until a Theme setter
+        // operation gets executed and modify it.
+        context.setTheme(Theme.UNSPECIFIED);
+
+        context.mRemoteComposeState = mRemoteComposeState;
+        if (mContentSizing == RootContentBehavior.SIZING_SCALE) {
+            // we need to add canvas transforms ops here
+            computeScale(context.mWidth, context.mHeight, mScaleOutput);
+            computeTranslate(context.mWidth, context.mHeight,
+                    mScaleOutput[0], mScaleOutput[1], mTranslateOutput);
+            context.mPaintContext.translate(mTranslateOutput[0], mTranslateOutput[1]);
+            context.mPaintContext.scale(mScaleOutput[0], mScaleOutput[1]);
+        }
+        for (Operation op : mOperations) {
+            // operations will only be executed if no theme is set (ie UNSPECIFIED)
+            // or the theme is equal as the one passed in argument to paint.
+            boolean apply = true;
+            if (theme != Theme.UNSPECIFIED) {
+                apply = op instanceof Theme // always apply a theme setter
+                        || context.getTheme() == theme
+                        || context.getTheme() == Theme.UNSPECIFIED;
+            }
+            if (apply) {
+                op.apply(context);
+            }
+        }
+        context.mMode = RemoteContext.ContextMode.UNSET;
+    }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
new file mode 100644
index 0000000..7cb9a42
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Base interface for RemoteCompose operations
+ */
+public interface Operation {
+
+    /**
+     * add the operation to the buffer
+     */
+    void write(WireBuffer buffer);
+
+    /**
+     * paint an operation
+     *
+     * @param context the paint context used to paint the operation
+     */
+    void apply(RemoteContext context);
+
+    /**
+     * Debug utility to display an operation + indentation
+     */
+    String deepToString(String indent);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
new file mode 100644
index 0000000..b8bb1f0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.Header;
+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;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+
+/**
+ * List of operations supported in a RemoteCompose document
+ */
+public class Operations {
+
+    ////////////////////////////////////////
+    // Protocol
+    ////////////////////////////////////////
+    public static final int HEADER = 0;
+    public static final int LOAD_BITMAP = 4;
+    public static final int THEME = 63;
+    public static final int CLICK_AREA = 64;
+    public static final int ROOT_CONTENT_BEHAVIOR = 65;
+    public static final int ROOT_CONTENT_DESCRIPTION = 103;
+
+    ////////////////////////////////////////
+    // Draw commands
+    ////////////////////////////////////////
+    public static final int DRAW_BITMAP = 44;
+    public static final int DRAW_BITMAP_INT = 66;
+    public static final int DATA_BITMAP = 101;
+    public static final int DATA_TEXT = 102;
+
+
+    public static IntMap<CompanionOperation> map = new IntMap<>();
+
+    static {
+        map.put(HEADER, Header.COMPANION);
+        map.put(DRAW_BITMAP_INT, DrawBitmapInt.COMPANION);
+        map.put(DATA_BITMAP, BitmapData.COMPANION);
+        map.put(DATA_TEXT, TextData.COMPANION);
+        map.put(THEME, Theme.COMPANION);
+        map.put(CLICK_AREA, ClickArea.COMPANION);
+        map.put(ROOT_CONTENT_BEHAVIOR, RootContentBehavior.COMPANION);
+        map.put(ROOT_CONTENT_DESCRIPTION, RootContentDescription.COMPANION);
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
new file mode 100644
index 0000000..6999cde
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Specify an abstract paint context used by RemoteCompose commands to draw
+ */
+public abstract class PaintContext {
+    protected RemoteContext mContext;
+
+    public PaintContext(RemoteContext context) {
+        this.mContext = context;
+    }
+
+    public void setContext(RemoteContext context) {
+        this.mContext = context;
+    }
+
+    public abstract void drawBitmap(int imageId,
+                             int srcLeft, int srcTop, int srcRight, int srcBottom,
+                             int dstLeft, int dstTop, int dstRight, int dstBottom,
+                             int cdId);
+
+    public abstract void scale(float scaleX, float scaleY);
+    public abstract void translate(float translateX, float translateY);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
new file mode 100644
index 0000000..2f3fe57
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -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.internal.widget.remotecompose.core;
+
+/**
+ * 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 {
+
+    @Override
+    public void apply(RemoteContext context) {
+        if (context.getMode() == RemoteContext.ContextMode.PAINT
+                && context.getPaintContext() != null) {
+            paint((PaintContext) context.getPaintContext());
+        }
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+    public abstract void paint(PaintContext context);
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
similarity index 67%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
rename to core/java/com/android/internal/widget/remotecompose/core/Platform.java
index a368de2..abda0c0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.internal.widget.remotecompose.core;
 
-package com.android.credentialmanager.ui.model
+/**
+ * Services that are needed to be provided by the platform during encoding.
+ */
+public interface Platform {
+    byte[] imageToByteArray(Object image);
+}
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
new file mode 100644
index 0000000..3ab6c47
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -0,0 +1,317 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.Header;
+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;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Provides an abstract buffer to encode/decode RemoteCompose operations
+ */
+public class RemoteComposeBuffer {
+    WireBuffer mBuffer = new WireBuffer();
+    Platform mPlatform = null;
+    RemoteComposeState mRemoteComposeState;
+
+    /**
+     * Provides an abstract buffer to encode/decode RemoteCompose operations
+     *
+     * @param remoteComposeState the state used while encoding on the buffer
+     */
+    public RemoteComposeBuffer(RemoteComposeState remoteComposeState) {
+        this.mRemoteComposeState = remoteComposeState;
+    }
+
+    public void reset() {
+        mBuffer.reset();
+        mRemoteComposeState.reset();
+    }
+
+    public Platform getPlatform() {
+        return mPlatform;
+    }
+
+    public void setPlatform(Platform platform) {
+        this.mPlatform = platform;
+    }
+
+    public WireBuffer getBuffer() {
+        return mBuffer;
+    }
+
+    public void setBuffer(WireBuffer buffer) {
+        this.mBuffer = buffer;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Supported operations on the buffer
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Insert a header
+     *
+     * @param width        the width of the document in pixels
+     * @param height       the height of the document in pixels
+     * @param contentDescription content description of the document
+     * @param capabilities bitmask indicating needed capabilities (unused for now)
+     */
+    public void header(int width, int height, String contentDescription, long capabilities) {
+        Header.COMPANION.apply(mBuffer, width, height, capabilities);
+        int contentDescriptionId = 0;
+        if (contentDescription != null) {
+            contentDescriptionId = addText(contentDescription);
+            RootContentDescription.COMPANION.apply(mBuffer, contentDescriptionId);
+        }
+    }
+
+    /**
+     * Insert a header
+     *
+     * @param width  the width of the document in pixels
+     * @param height the height of the document in pixels
+     * @param contentDescription content description of the document
+     */
+    public void header(int width, int height, String contentDescription) {
+        header(width, height, contentDescription, 0);
+    }
+
+    /**
+     * Insert a bitmap
+     *
+     * @param image       an opaque image that we'll add to the buffer
+     * @param imageWidth the width of the image
+     * @param imageHeight the height of the image
+     * @param srcLeft     left coordinate of the source area
+     * @param srcTop      top coordinate of the source area
+     * @param srcRight    right coordinate of the source area
+     * @param srcBottom   bottom coordinate of the source area
+     * @param dstLeft     left coordinate of the destination area
+     * @param dstTop      top coordinate of the destination area
+     * @param dstRight    right coordinate of the destination area
+     * @param dstBottom   bottom coordinate of the destination area
+     */
+    public void drawBitmap(Object image,
+                           int imageWidth, int imageHeight,
+                           int srcLeft, int srcTop, int srcRight, int srcBottom,
+                           int dstLeft, int dstTop, int dstRight, int dstBottom,
+                           String contentDescription) {
+        int imageId = mRemoteComposeState.dataGetId(image);
+        if (imageId == -1) {
+            imageId = mRemoteComposeState.cache(image);
+            byte[] data = mPlatform.imageToByteArray(image);
+            BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+        }
+        int contentDescriptionId = 0;
+        if (contentDescription != null) {
+            contentDescriptionId = addText(contentDescription);
+        }
+        DrawBitmapInt.COMPANION.apply(
+                mBuffer, imageId, srcLeft, srcTop, srcRight, srcBottom,
+                dstLeft, dstTop, dstRight, dstBottom, contentDescriptionId
+        );
+    }
+
+    /**
+     * Adds a text string data to the stream and returns its id
+     * Will be used to insert string with bitmaps etc.
+     *
+     * @param text the string to inject in the buffer
+     */
+    int addText(String text) {
+        int id = mRemoteComposeState.dataGetId(text);
+        if (id == -1) {
+            id = mRemoteComposeState.cache(text);
+            TextData.COMPANION.apply(mBuffer, id, text);
+        }
+        return id;
+    }
+
+    /**
+     * Add a click area to the document
+     *
+     * @param id       the id of the click area, reported in the click listener callback
+     * @param contentDescription the content description of that click area (accessibility)
+     * @param left     left coordinate of the area bounds
+     * @param top      top coordinate of the area bounds
+     * @param right    right coordinate of the area bounds
+     * @param bottom   bottom coordinate of the area bounds
+     * @param metadata associated metadata, user-provided
+     */
+    public void addClickArea(
+            int id,
+            String contentDescription,
+            float left,
+            float top,
+            float right,
+            float bottom,
+            String metadata
+    ) {
+        int contentDescriptionId = 0;
+        if (contentDescription != null) {
+            contentDescriptionId = addText(contentDescription);
+        }
+        int metadataId = 0;
+        if (metadata != null) {
+            metadataId = addText(metadata);
+        }
+        ClickArea.COMPANION.apply(mBuffer, id, contentDescriptionId,
+                left, top, right, bottom, metadataId);
+    }
+
+    /**
+     * Sets the way the player handles the content
+     *
+     * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+     * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+     * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+     * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+     *             the LAYOUT modes are:
+     *             - LAYOUT_MATCH_PARENT
+     *             - LAYOUT_WRAP_CONTENT
+     *             or adding an horizontal mode and a vertical mode:
+     *             - LAYOUT_HORIZONTAL_MATCH_PARENT
+     *             - LAYOUT_HORIZONTAL_WRAP_CONTENT
+     *             - LAYOUT_HORIZONTAL_FIXED
+     *             - LAYOUT_VERTICAL_MATCH_PARENT
+     *             - LAYOUT_VERTICAL_WRAP_CONTENT
+     *             - LAYOUT_VERTICAL_FIXED
+     *             The LAYOUT_*_FIXED modes will use the intrinsic document size
+     */
+    public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+        RootContentBehavior.COMPANION.apply(mBuffer, scroll, alignment, sizing, mode);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void inflateFromBuffer(ArrayList<Operation> operations) {
+        mBuffer.setIndex(0);
+        while (mBuffer.available()) {
+            int opId = mBuffer.readByte();
+            CompanionOperation operation = Operations.map.get(opId);
+            if (operation == null) {
+                throw new RuntimeException("Unknown operation encountered");
+            }
+            operation.read(mBuffer, operations);
+        }
+    }
+
+    RemoteComposeBuffer copy() {
+        ArrayList<Operation> operations = new ArrayList<>();
+        inflateFromBuffer(operations);
+        RemoteComposeBuffer buffer = new RemoteComposeBuffer(mRemoteComposeState);
+        return copyFromOperations(operations, buffer);
+    }
+
+    public void setTheme(int theme) {
+        Theme.COMPANION.apply(mBuffer, theme);
+    }
+
+
+    static String version() {
+        return "v1.0";
+    }
+
+    public static RemoteComposeBuffer fromFile(String path,
+                                               RemoteComposeState remoteComposeState)
+            throws IOException {
+        RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
+        read(new File(path), buffer);
+        return buffer;
+    }
+
+    public RemoteComposeBuffer fromFile(File file,
+                                        RemoteComposeState remoteComposeState) throws IOException {
+        RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
+        read(file, buffer);
+        return buffer;
+    }
+
+    public static RemoteComposeBuffer fromInputStream(InputStream inputStream,
+                                               RemoteComposeState remoteComposeState) {
+        RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
+        read(inputStream, buffer);
+        return buffer;
+    }
+
+    RemoteComposeBuffer copyFromOperations(ArrayList<Operation> operations,
+                                           RemoteComposeBuffer buffer) {
+
+        for (Operation operation : operations) {
+            operation.write(buffer.mBuffer);
+        }
+        return buffer;
+    }
+
+    public void write(RemoteComposeBuffer buffer, File file) {
+        try {
+            FileOutputStream fd = new FileOutputStream(file);
+            fd.write(buffer.mBuffer.getBuffer(), 0, buffer.mBuffer.getSize());
+            fd.flush();
+            fd.close();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    static void read(File file, RemoteComposeBuffer buffer) throws IOException {
+        FileInputStream fd = new FileInputStream(file);
+        read(fd, buffer);
+    }
+
+    public static void read(InputStream fd, RemoteComposeBuffer buffer) {
+        try {
+            byte[] bytes = readAllBytes(fd);
+            buffer.reset();
+            System.arraycopy(bytes, 0, buffer.mBuffer.mBuffer, 0, bytes.length);
+            buffer.mBuffer.mSize = bytes.length;
+        } catch (Exception e) {
+            e.printStackTrace();
+            // todo decide how to handel this stuff
+        }
+    }
+
+    private static byte[] readAllBytes(InputStream is) throws IOException {
+        byte[] buff = new byte[32 * 1024]; // moderate size buff to start
+        int red = 0;
+        while (true) {
+            int ret = is.read(buff, red, buff.length - red);
+            if (ret == -1) {
+                is.close();
+                return Arrays.copyOf(buff, red);
+            }
+            red += ret;
+            if (red == buff.length) {
+                buff = Arrays.copyOf(buff, buff.length * 2);
+            }
+        }
+    }
+
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
similarity index 76%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
rename to core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
index 52bbfaa..c7ec3359 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.internal.widget.remotecompose.core;
 
-package com.android.credentialmanager.ui.model
+public interface RemoteComposeOperation extends Operation {
 
-data class PasswordUiModel(val email: String)
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
new file mode 100644
index 0000000..17e8c83
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+
+import java.util.HashMap;
+
+/**
+ * Represents runtime state for a RemoteCompose document
+ */
+public class RemoteComposeState {
+
+    private final IntMap<Object> mIntDataMap = new IntMap<>();
+    private final IntMap<Boolean> mIntWrittenMap = new IntMap<>();
+    private final HashMap<Object, Integer> mDataIntMap = new HashMap();
+
+    private static int sNextId = 42;
+
+    public Object getFromId(int id)  {
+        return mIntDataMap.get(id);
+    }
+
+    public boolean containsId(int id)  {
+        return mIntDataMap.get(id) != null;
+    }
+
+    /**
+     * Return the id of an item from the cache.
+     */
+    public int dataGetId(Object image) {
+        Integer res = mDataIntMap.get(image);
+        if (res == null) {
+            return -1;
+        }
+        return res;
+    }
+
+    /**
+     * Add an image to the cache. Generates an id for the image and adds it to the cache based on
+     * that id.
+     */
+    public int cache(Object image) {
+        int id = nextId();
+        mDataIntMap.put(image, id);
+        mIntDataMap.put(id, image);
+        return id;
+    }
+
+    /**
+     * Insert an item in the cache
+     */
+    public void cache(int id, Object item) {
+        mDataIntMap.put(item, id);
+        mIntDataMap.put(id, item);
+    }
+
+    /**
+     * Method to determine if a cached value has been written to the documents WireBuffer based on
+     * its id.
+     */
+    public boolean wasNotWritten(int id) {
+        return !mIntWrittenMap.get(id);
+    }
+
+    /**
+     * Method to mark that a value, represented by its id, has been written to the WireBuffer
+     */
+    public void  markWritten(int id) {
+        mIntWrittenMap.put(id, true);
+    }
+
+    /**
+     *  Clear the record of the values that have been written to the WireBuffer.
+     */
+    void reset() {
+        mIntWrittenMap.clear();
+    }
+
+    public static int nextId() {
+        return sNextId++;
+    }
+    public static void setNextId(int id) {
+        sNextId = id;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
new file mode 100644
index 0000000..1b7c6fd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.operations.Theme;
+
+/**
+ * Specify an abstract context used to playback RemoteCompose documents
+ *
+ * This allows us to intercept the different operations in a document and react to them.
+ *
+ * We also contain a PaintContext, so that any operation can draw as needed.
+ */
+public abstract class RemoteContext {
+    protected CoreDocument mDocument;
+    public RemoteComposeState mRemoteComposeState;
+
+    protected PaintContext mPaintContext = null;
+    ContextMode mMode = ContextMode.UNSET;
+
+    boolean mDebug = false;
+    private int mTheme = Theme.UNSPECIFIED;
+
+    public float mWidth = 0f;
+    public float mHeight = 0f;
+
+    /**
+     * 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 loading a bitmap) should execute
+     * - PAINT : only operations painting should execute
+     */
+    public enum  ContextMode {
+        UNSET, DATA, PAINT
+    }
+
+    public int getTheme() {
+        return mTheme;
+    }
+
+    public void setTheme(int theme) {
+        this.mTheme = theme;
+    }
+
+    public ContextMode getMode() {
+        return mMode;
+    }
+
+    public void setMode(ContextMode mode) {
+        this.mMode = mode;
+    }
+
+    public PaintContext getPaintContext() {
+        return mPaintContext;
+    }
+
+    public void setPaintContext(PaintContext paintContext) {
+        this.mPaintContext = paintContext;
+    }
+
+    public CoreDocument getDocument() {
+        return mDocument;
+    }
+
+    public boolean isDebug() {
+        return mDebug;
+    }
+
+    public void setDebug(boolean debug) {
+        this.mDebug = debug;
+    }
+
+    public void setDocument(CoreDocument document) {
+        this.mDocument = document;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Operations
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void header(int majorVersion, int minorVersion, int patchVersion,
+                       int width, int height, long capabilities
+    ) {
+        mDocument.setVersion(majorVersion, minorVersion, patchVersion);
+        mDocument.setWidth(width);
+        mDocument.setHeight(height);
+        mDocument.setRequiredCapabilities(capabilities);
+    }
+
+    /**
+     * Sets the way the player handles the content
+     *
+     * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+     * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+     * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+     * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+     *             the LAYOUT modes are:
+     *             - LAYOUT_MATCH_PARENT
+     *             - LAYOUT_WRAP_CONTENT
+     *             or adding an horizontal mode and a vertical mode:
+     *             - LAYOUT_HORIZONTAL_MATCH_PARENT
+     *             - LAYOUT_HORIZONTAL_WRAP_CONTENT
+     *             - LAYOUT_HORIZONTAL_FIXED
+     *             - LAYOUT_VERTICAL_MATCH_PARENT
+     *             - LAYOUT_VERTICAL_WRAP_CONTENT
+     *             - LAYOUT_VERTICAL_FIXED
+     *             The LAYOUT_*_FIXED modes will use the intrinsic document size
+     */
+    public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+        mDocument.setRootContentBehavior(scroll, alignment, sizing, mode);
+    }
+
+    /**
+     * Set a content description for the document
+     * @param contentDescriptionId the text id pointing at the description
+     */
+    public void setDocumentContentDescription(int contentDescriptionId) {
+        String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId);
+        mDocument.setContentDescription(contentDescription);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Data handling
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    public abstract void loadBitmap(int imageId, int width, int height, byte[] bitmap);
+    public abstract void loadText(int id, String text);
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Click handling
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    public abstract void addClickArea(
+            int id,
+            int contentDescription,
+            float left,
+            float top,
+            float right,
+            float bottom,
+            int metadataId
+    );
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
new file mode 100644
index 0000000..3e701c1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -0,0 +1,254 @@
+/*
+ * 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 java.util.Arrays;
+
+/**
+ * The base communication buffer capable of encoding and decoding various types
+ */
+public class WireBuffer {
+    private static final int BUFFER_SIZE = 1024 * 1024 * 1;
+    int mMaxSize;
+    byte[] mBuffer;
+    int mIndex = 0;
+    int mStartingIndex = 0;
+    int mSize = 0;
+
+    public WireBuffer(int size) {
+        mMaxSize = size;
+        mBuffer = new byte[mMaxSize];
+    }
+
+    public WireBuffer() {
+        this(BUFFER_SIZE);
+    }
+
+    private void resize(int need) {
+        if (mSize + need >= mMaxSize) {
+            mMaxSize = Math.max(mMaxSize * 2, mSize + need);
+            mBuffer = Arrays.copyOf(mBuffer, mMaxSize);
+        }
+    }
+
+    public byte[] getBuffer() {
+        return mBuffer;
+    }
+
+    public int getMax_size() {
+        return mMaxSize;
+    }
+
+    public int getIndex() {
+        return mIndex;
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+
+    public void setIndex(int index) {
+        this.mIndex = index;
+    }
+
+    public void start(int type) {
+        mStartingIndex = mIndex;
+        writeByte(type);
+    }
+
+    public void startWithSize(int type) {
+        mStartingIndex = mIndex;
+        writeByte(type);
+        mIndex += 4; // skip ahead for the future size
+    }
+
+    public void endWithSize() {
+        int size = mIndex - mStartingIndex;
+        int currentIndex = mIndex;
+        mIndex = mStartingIndex + 1; // (type)
+        writeInt(size);
+        mIndex = currentIndex;
+    }
+
+    public void reset() {
+        mIndex = 0;
+        mStartingIndex = 0;
+        mSize = 0;
+    }
+
+    public int size() {
+        return mSize;
+    }
+
+    public boolean available() {
+        return mSize - mIndex > 0;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Read values
+    ///////////////////////////////////////////////////////////////////////////
+
+    public int readOperationType() {
+        return readByte();
+    }
+
+    public boolean readBoolean() {
+        byte value = mBuffer[mIndex];
+        mIndex++;
+        return (value == 1);
+    }
+
+    public int readByte() {
+        byte value = mBuffer[mIndex];
+        mIndex++;
+        return value;
+    }
+
+    public int readShort() {
+        int v1 = (mBuffer[mIndex++] & 0xFF) << 8;
+        int v2 = (mBuffer[mIndex++] & 0xFF) << 0;
+        return v1 + v2;
+    }
+
+    public int readInt() {
+        int v1 = (mBuffer[mIndex++] & 0xFF) << 24;
+        int v2 = (mBuffer[mIndex++] & 0xFF) << 16;
+        int v3 = (mBuffer[mIndex++] & 0xFF) << 8;
+        int v4 = (mBuffer[mIndex++] & 0xFF) << 0;
+        return v1 + v2 + v3 + v4;
+    }
+
+    public long readLong() {
+        long v1 = (mBuffer[mIndex++] & 0xFFL) << 56;
+        long v2 = (mBuffer[mIndex++] & 0xFFL) << 48;
+        long v3 = (mBuffer[mIndex++] & 0xFFL) << 40;
+        long v4 = (mBuffer[mIndex++] & 0xFFL) << 32;
+        long v5 = (mBuffer[mIndex++] & 0xFFL) << 24;
+        long v6 = (mBuffer[mIndex++] & 0xFFL) << 16;
+        long v7 = (mBuffer[mIndex++] & 0xFFL) << 8;
+        long v8 = (mBuffer[mIndex++] & 0xFFL) << 0;
+        return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;
+    }
+
+    public float readFloat() {
+        return java.lang.Float.intBitsToFloat(readInt());
+    }
+
+    public double readDouble() {
+        return java.lang.Double.longBitsToDouble(readLong());
+    }
+
+    public byte[] readBuffer() {
+        int count = readInt();
+        byte[] b = Arrays.copyOfRange(mBuffer, mIndex, mIndex + count);
+        mIndex += count;
+        return b;
+    }
+
+    public byte[] readBuffer(int maxSize) {
+        int count = readInt();
+        if (count < 0 || count > maxSize) {
+            throw new RuntimeException("attempt read a buff of invalid size 0 <= "
+                    + count + " > " + maxSize);
+        }
+        byte[] b = Arrays.copyOfRange(mBuffer, mIndex, mIndex + count);
+        mIndex += count;
+        return b;
+    }
+
+    public String readUTF8() {
+        byte[] stringBuffer = readBuffer();
+        return new String(stringBuffer);
+    }
+
+    public String readUTF8(int maxSize) {
+        byte[] stringBuffer = readBuffer(maxSize);
+        return new String(stringBuffer);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Write values
+    ///////////////////////////////////////////////////////////////////////////
+
+    public void writeBoolean(boolean value) {
+        resize(1);
+        mBuffer[mIndex++] = (byte) ((value) ? 1 : 0);
+        mSize++;
+    }
+
+    public void writeByte(int value) {
+        resize(1);
+        mBuffer[mIndex++] = (byte) value;
+        mSize++;
+    }
+
+    public void writeShort(int value) {
+        int need = 2;
+        resize(need);
+        mBuffer[mIndex++] = (byte) (value >>> 8 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value & 0xFF);
+        mSize += need;
+    }
+
+    public void writeInt(int value) {
+        int need = 4;
+        resize(need);
+        mBuffer[mIndex++] = (byte) (value >>> 24 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 16 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 8 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value & 0xFF);
+        mSize += need;
+    }
+
+    public void writeLong(long value) {
+        int need = 8;
+        resize(need);
+        mBuffer[mIndex++] = (byte) (value >>> 56 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 48 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 40 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 32 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 24 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 16 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value >>> 8 & 0xFF);
+        mBuffer[mIndex++] = (byte) (value & 0xFF);
+        mSize += need;
+    }
+
+    public void writeFloat(float value) {
+        writeInt(Float.floatToRawIntBits(value));
+    }
+
+    public void writeDouble(double value) {
+        writeLong(Double.doubleToRawLongBits(value));
+    }
+
+    public void writeBuffer(byte[] b) {
+        resize(b.length + 4);
+        writeInt(b.length);
+        for (int i = 0; i < b.length; i++) {
+            mBuffer[mIndex++] = b[i];
+
+        }
+        mSize += b.length;
+    }
+
+    public void writeUTF8(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
new file mode 100644
index 0000000..4bfdc59
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -0,0 +1,104 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.CompanionOperation;
+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 java.util.List;
+
+/**
+ * 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 {
+    int mImageId;
+    int mImageWidth;
+    int mImageHeight;
+    byte[] mBitmap;
+    public static final int MAX_IMAGE_DIMENSION = 6000;
+
+    public static final Companion COMPANION = new Companion();
+
+    public BitmapData(int imageId, int width, int height, byte[] bitmap) {
+        this.mImageId = imageId;
+        this.mImageWidth = width;
+        this.mImageHeight = height;
+        this.mBitmap = bitmap;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mImageId, mImageWidth, mImageHeight, mBitmap);
+    }
+
+    @Override
+    public String toString() {
+        return "BITMAP DATA $imageId";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "BitmapData";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_BITMAP;
+        }
+
+        public void apply(WireBuffer buffer, int imageId, int width, int height, byte[] bitmap) {
+            buffer.start(Operations.DATA_BITMAP);
+            buffer.writeInt(imageId);
+            buffer.writeInt(width);
+            buffer.writeInt(height);
+            buffer.writeBuffer(bitmap);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int imageId = buffer.readInt();
+            int width = buffer.readInt();
+            int height = buffer.readInt();
+            if (width < 1
+                    || height < 1
+                    || height > MAX_IMAGE_DIMENSION
+                    || width > MAX_IMAGE_DIMENSION) {
+                throw new RuntimeException("Dimension of image is invalid " + width + "x" + height);
+            }
+            byte[] bitmap = buffer.readBuffer();
+            operations.add(new BitmapData(imageId, width, height, bitmap));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.loadBitmap(mImageId, mImageWidth, mImageHeight, mBitmap);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+}
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
new file mode 100644
index 0000000..a3cd197
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+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.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Add a click area to the document
+ */
+public class ClickArea implements RemoteComposeOperation {
+    int mId;
+    int mContentDescription;
+    float mLeft;
+    float mTop;
+    float mRight;
+    float mBottom;
+    int mMetadata;
+
+    public static final Companion COMPANION = new Companion();
+
+    /**
+     * Add a click area to the document
+     *
+     * @param id       the id of the click area, which will be reported in the listener
+     *                 callback on the player
+     * @param contentDescription the content description (used for accessibility, as a textID)
+     * @param left     left coordinate of the area bounds
+     * @param top      top coordinate of the area bounds
+     * @param right    right coordinate of the area bounds
+     * @param bottom   bottom coordinate of the area bounds
+     * @param metadata associated metadata, user-provided (as a textID, pointing to a string)
+     */
+    public ClickArea(int id, int contentDescription,
+                     float left, float top,
+                     float right, float bottom,
+                     int metadata) {
+        this.mId = id;
+        this.mContentDescription = contentDescription;
+        this.mLeft = left;
+        this.mTop = top;
+        this.mRight = right;
+        this.mBottom = bottom;
+        this.mMetadata = metadata;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
+    }
+
+    @Override
+    public String toString() {
+        return "CLICK_AREA <" + mId + " <" + mContentDescription + "> "
+                + "<" + mMetadata + ">+" + mLeft + " "
+                + mTop + " " + mRight + " " + mBottom + "+"
+                + " (" + (mRight - mLeft) + " x " + (mBottom - mTop) + " }";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        if (context.getMode() != RemoteContext.ContextMode.DATA) {
+            return;
+        }
+        context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "ClickArea";
+        }
+
+        @Override
+        public int id() {
+            return Operations.CLICK_AREA;
+        }
+
+        public void apply(WireBuffer buffer, int id, int contentDescription,
+                   float left, float top, float right, float bottom,
+                   int metadata) {
+            buffer.start(Operations.CLICK_AREA);
+            buffer.writeInt(id);
+            buffer.writeInt(contentDescription);
+            buffer.writeFloat(left);
+            buffer.writeFloat(top);
+            buffer.writeFloat(right);
+            buffer.writeFloat(bottom);
+            buffer.writeInt(metadata);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int id = buffer.readInt();
+            int contentDescription = buffer.readInt();
+            float left = buffer.readFloat();
+            float top = buffer.readFloat();
+            float right = buffer.readFloat();
+            float bottom = buffer.readFloat();
+            int metadata = buffer.readInt();
+            ClickArea clickArea = new ClickArea(id, contentDescription,
+                    left, top, right, bottom, metadata);
+            operations.add(clickArea);
+        }
+    }
+}
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
new file mode 100644
index 0000000..3fbdf94
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -0,0 +1,132 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.CompanionOperation;
+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.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to draw a given cached bitmap
+ */
+public class DrawBitmapInt extends PaintOperation {
+    int mImageId;
+    int mSrcLeft;
+    int mSrcTop;
+    int mSrcRight;
+    int mSrcBottom;
+    int mDstLeft;
+    int mDstTop;
+    int mDstRight;
+    int mDstBottom;
+    int mContentDescId = 0;
+    public static final Companion COMPANION = new Companion();
+
+    public DrawBitmapInt(int imageId,
+                         int srcLeft,
+                         int srcTop,
+                         int srcRight,
+                         int srcBottom,
+                         int dstLeft,
+                         int dstTop,
+                         int dstRight,
+                         int dstBottom,
+                         int cdId) {
+        this.mImageId = imageId;
+        this.mSrcLeft = srcLeft;
+        this.mSrcTop = srcTop;
+        this.mSrcRight = srcRight;
+        this.mSrcBottom = srcBottom;
+        this.mDstLeft = dstLeft;
+        this.mDstTop = dstTop;
+        this.mDstRight = dstRight;
+        this.mDstBottom = dstBottom;
+        this.mContentDescId = cdId;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mImageId, mSrcLeft, mSrcTop, mSrcRight, mSrcBottom,
+                mDstLeft, mDstTop, mDstRight, mDstBottom, mContentDescId);
+    }
+
+    @Override
+    public String toString() {
+        return "DRAW_BITMAP_INT " + mImageId + " on " + mSrcLeft + " " + mSrcTop
+                + " " + mSrcRight + " " + mSrcBottom + " "
+                + "- " + mDstLeft + " " + mDstTop + " " + mDstRight + " " + mDstBottom + ";";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "DrawBitmapInt";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DRAW_BITMAP;
+        }
+
+        public void apply(WireBuffer buffer, int imageId,
+                   int srcLeft, int srcTop, int srcRight, int srcBottom,
+                   int dstLeft, int dstTop, int dstRight, int dstBottom,
+                   int cdId) {
+            buffer.start(Operations.DRAW_BITMAP_INT);
+            buffer.writeInt(imageId);
+            buffer.writeInt(srcLeft);
+            buffer.writeInt(srcTop);
+            buffer.writeInt(srcRight);
+            buffer.writeInt(srcBottom);
+            buffer.writeInt(dstLeft);
+            buffer.writeInt(dstTop);
+            buffer.writeInt(dstRight);
+            buffer.writeInt(dstBottom);
+            buffer.writeInt(cdId);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int imageId = buffer.readInt();
+            int sLeft = buffer.readInt();
+            int srcTop = buffer.readInt();
+            int srcRight = buffer.readInt();
+            int srcBottom = buffer.readInt();
+            int dstLeft = buffer.readInt();
+            int dstTop = buffer.readInt();
+            int dstRight = buffer.readInt();
+            int dstBottom = buffer.readInt();
+            int cdId = buffer.readInt();
+            DrawBitmapInt op = new DrawBitmapInt(imageId, sLeft, srcTop, srcRight, srcBottom,
+                    dstLeft, dstTop, dstRight, dstBottom, cdId);
+
+            operations.add(op);
+        }
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        context.drawBitmap(mImageId, mSrcLeft, mSrcTop, mSrcRight, mSrcBottom,
+                mDstLeft, mDstTop, mDstRight, mDstBottom, mContentDescId);
+    }
+}
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
new file mode 100644
index 0000000..eca43c5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -0,0 +1,127 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+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.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Describe some basic information for a RemoteCompose document
+ *
+ * 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 static final int MAJOR_VERSION = 0;
+    public static final int MINOR_VERSION = 1;
+    public static final int PATCH_VERSION = 0;
+
+    int mMajorVersion;
+    int mMinorVersion;
+    int mPatchVersion;
+
+    int mWidth;
+    int mHeight;
+    long mCapabilities;
+
+    public static final Companion COMPANION = new Companion();
+
+    /**
+     * It encodes the version of the document (following semantic versioning) as well
+     * as the dimensions of the document in pixels.
+     *
+     * @param majorVersion the major version of the RemoteCompose document API
+     * @param minorVersion the minor version of the RemoteCompose document API
+     * @param patchVersion the patch version of the RemoteCompose document API
+     * @param width        the width of the RemoteCompose document
+     * @param height       the height of the RemoteCompose document
+     * @param capabilities bitmask field storing needed capabilities (unused for now)
+     */
+    public Header(int majorVersion, int minorVersion, int patchVersion,
+                  int width, int height, long capabilities) {
+        this.mMajorVersion = majorVersion;
+        this.mMinorVersion = minorVersion;
+        this.mPatchVersion = patchVersion;
+        this.mWidth = width;
+        this.mHeight = height;
+        this.mCapabilities = capabilities;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mWidth, mHeight, mCapabilities);
+    }
+
+    @Override
+    public String toString() {
+        return "HEADER v" + mMajorVersion + "."
+                + mMinorVersion + "." + mPatchVersion + ", "
+                + mWidth + " x " + mHeight + " [" + mCapabilities + "]";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.header(mMajorVersion, mMinorVersion, mPatchVersion, mWidth, mHeight, mCapabilities);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return toString();
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "Header";
+        }
+
+        @Override
+        public int id() {
+            return Operations.HEADER;
+        }
+
+        public void apply(WireBuffer buffer, int width, int height, long capabilities) {
+            buffer.start(Operations.HEADER);
+            buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
+            buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
+            buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
+            buffer.writeInt(width);
+            buffer.writeInt(height);
+            buffer.writeLong(capabilities);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int majorVersion = buffer.readInt();
+            int minorVersion = buffer.readInt();
+            int patchVersion = buffer.readInt();
+            int width = buffer.readInt();
+            int height = buffer.readInt();
+            long capabilities = buffer.readLong();
+            Header header = new Header(majorVersion, minorVersion, patchVersion,
+                    width, height, capabilities);
+            operations.add(header);
+        }
+    }
+}
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
new file mode 100644
index 0000000..ad4caea
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import android.util.Log;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+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.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Describe some basic information for a RemoteCompose document
+ *
+ * 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 {
+
+    int mScroll = NONE;
+    int mSizing = NONE;
+
+    int mAlignment = ALIGNMENT_CENTER;
+
+    int mMode = NONE;
+
+    protected static final String TAG = "RootContentBehavior";
+
+    public static final int NONE = 0;
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Scrolling
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    public static final int SCROLL_HORIZONTAL = 1;
+    public static final int SCROLL_VERTICAL = 2;
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Sizing
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    public static final int SIZING_LAYOUT = 1;
+    public static final int SIZING_SCALE = 2;
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Sizing
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    public static final int ALIGNMENT_TOP = 1;
+    public static final int ALIGNMENT_VERTICAL_CENTER = 2;
+    public static final int ALIGNMENT_BOTTOM = 4;
+    public static final int ALIGNMENT_START = 16;
+    public static final int ALIGNMENT_HORIZONTAL_CENTER = 32;
+    public static final int ALIGNMENT_END = 64;
+    public static final int ALIGNMENT_CENTER = ALIGNMENT_HORIZONTAL_CENTER
+            + ALIGNMENT_VERTICAL_CENTER;
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Layout mode
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    public static final int LAYOUT_HORIZONTAL_MATCH_PARENT = 1;
+    public static final int LAYOUT_HORIZONTAL_WRAP_CONTENT = 2;
+    public static final int LAYOUT_HORIZONTAL_FIXED = 4;
+    public static final int LAYOUT_VERTICAL_MATCH_PARENT = 8;
+    public static final int LAYOUT_VERTICAL_WRAP_CONTENT = 16;
+    public static final int LAYOUT_VERTICAL_FIXED = 32;
+    public static final int LAYOUT_MATCH_PARENT =
+            LAYOUT_HORIZONTAL_MATCH_PARENT + LAYOUT_VERTICAL_MATCH_PARENT;
+    public static final int LAYOUT_WRAP_CONTENT =
+            LAYOUT_HORIZONTAL_WRAP_CONTENT + LAYOUT_VERTICAL_WRAP_CONTENT;
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Sizing mode
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static final int SCALE_INSIDE = 1;
+    public static final int SCALE_FILL_WIDTH = 2;
+    public static final int SCALE_FILL_HEIGHT = 3;
+    public static final int SCALE_FIT = 4;
+    public static final int SCALE_CROP = 5;
+    public static final int SCALE_FILL_BOUNDS = 6;
+
+
+    public static final Companion COMPANION = new Companion();
+
+    /**
+     * Sets the way the player handles the content
+     *
+     * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+     * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
+     * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+     * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+     *             the LAYOUT modes are:
+     *             - LAYOUT_MATCH_PARENT
+     *             - LAYOUT_WRAP_CONTENT
+     *             or adding an horizontal mode and a vertical mode:
+     *             - LAYOUT_HORIZONTAL_MATCH_PARENT
+     *             - LAYOUT_HORIZONTAL_WRAP_CONTENT
+     *             - LAYOUT_HORIZONTAL_FIXED
+     *             - LAYOUT_VERTICAL_MATCH_PARENT
+     *             - LAYOUT_VERTICAL_WRAP_CONTENT
+     *             - LAYOUT_VERTICAL_FIXED
+     *             The LAYOUT_*_FIXED modes will use the intrinsic document size
+     */
+    public RootContentBehavior(int scroll, int alignment, int sizing, int mode) {
+        switch (scroll) {
+            case NONE:
+            case SCROLL_HORIZONTAL:
+            case SCROLL_VERTICAL:
+                mScroll = scroll;
+                break;
+            default: {
+                Log.e(TAG, "incorrect scroll value " + scroll);
+            }
+        }
+        if (alignment == ALIGNMENT_CENTER) {
+            mAlignment = alignment;
+        } else {
+            int horizontalContentAlignment = alignment & 0xF0;
+            int verticalContentAlignment = alignment & 0xF;
+            boolean validHorizontalAlignment = horizontalContentAlignment == ALIGNMENT_START
+                    || horizontalContentAlignment == ALIGNMENT_HORIZONTAL_CENTER
+                    || horizontalContentAlignment == ALIGNMENT_END;
+            boolean validVerticalAlignment = verticalContentAlignment == ALIGNMENT_TOP
+                    || verticalContentAlignment == ALIGNMENT_VERTICAL_CENTER
+                    || verticalContentAlignment == ALIGNMENT_BOTTOM;
+            if (validHorizontalAlignment && validVerticalAlignment) {
+                mAlignment = alignment;
+            } else {
+                Log.e(TAG, "incorrect alignment "
+                        + " h: " + horizontalContentAlignment
+                        + " v: " + verticalContentAlignment);
+            }
+        }
+        switch (sizing) {
+            case SIZING_LAYOUT: {
+                Log.e(TAG, "sizing_layout is not yet supported");
+            } break;
+            case SIZING_SCALE: {
+                mSizing = sizing;
+            } break;
+            default: {
+                Log.e(TAG, "incorrect sizing value " + sizing);
+            }
+        }
+        if (mSizing == SIZING_LAYOUT) {
+            if (mode != NONE) {
+                Log.e(TAG, "mode for sizing layout is not yet supported");
+            }
+        } else if (mSizing == SIZING_SCALE) {
+            switch (mode) {
+                case SCALE_INSIDE:
+                case SCALE_FIT:
+                case SCALE_FILL_WIDTH:
+                case SCALE_FILL_HEIGHT:
+                case SCALE_CROP:
+                case SCALE_FILL_BOUNDS:
+                    mMode = mode;
+                    break;
+                default: {
+                    Log.e(TAG, "incorrect mode for scale sizing, mode: " + mode);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mScroll, mAlignment, mSizing, mMode);
+    }
+
+    @Override
+    public String toString() {
+        return "ROOT_CONTENT_BEHAVIOR scroll: " + mScroll
+                + " sizing: " + mSizing + " mode: " + mMode;
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.setRootContentBehavior(mScroll, mAlignment, mSizing, mMode);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return toString();
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "RootContentBehavior";
+        }
+
+        @Override
+        public int id() {
+            return Operations.ROOT_CONTENT_BEHAVIOR;
+        }
+
+        public void apply(WireBuffer buffer, int scroll, int alignment, int sizing, int mode) {
+            buffer.start(Operations.ROOT_CONTENT_BEHAVIOR);
+            buffer.writeInt(scroll);
+            buffer.writeInt(alignment);
+            buffer.writeInt(sizing);
+            buffer.writeInt(mode);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int scroll = buffer.readInt();
+            int alignment = buffer.readInt();
+            int sizing = buffer.readInt();
+            int mode = buffer.readInt();
+            RootContentBehavior rootContentBehavior =
+                    new RootContentBehavior(scroll, alignment, sizing, mode);
+            operations.add(rootContentBehavior);
+        }
+    }
+}
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
new file mode 100644
index 0000000..64c7f3e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+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.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Describe a content description for the document
+ */
+public class RootContentDescription implements RemoteComposeOperation {
+    int mContentDescription;
+
+    public static final Companion COMPANION = new Companion();
+
+    /**
+     * Encodes a content description for the document
+     *
+     * @param contentDescription content description for the document
+     */
+    public RootContentDescription(int contentDescription) {
+        this.mContentDescription = contentDescription;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mContentDescription);
+    }
+
+    @Override
+    public String toString() {
+        return "ROOT_CONTENT_DESCRIPTION " + mContentDescription;
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.setDocumentContentDescription(mContentDescription);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return toString();
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "RootContentDescription";
+        }
+
+        @Override
+        public int id() {
+            return Operations.ROOT_CONTENT_DESCRIPTION;
+        }
+
+        public void apply(WireBuffer buffer, int contentDescription) {
+            buffer.start(Operations.ROOT_CONTENT_DESCRIPTION);
+            buffer.writeInt(contentDescription);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int contentDescription = buffer.readInt();
+            RootContentDescription header = new RootContentDescription(contentDescription);
+            operations.add(header);
+        }
+    }
+}
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
new file mode 100644
index 0000000..5b622ae
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.android.internal.widget.remotecompose.core.CompanionOperation;
+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 java.util.List;
+
+/**
+ * Operation to deal with Text data
+ */
+public class TextData implements Operation {
+    public int mTextId;
+    public String mText;
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+
+    public TextData(int textId, String text) {
+        this.mTextId = textId;
+        this.mText = text;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTextId, mText);
+    }
+
+    @Override
+    public String toString() {
+        return "TEXT DATA " + mTextId + "\"" + mText + "\"";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "TextData";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_TEXT;
+        }
+
+        public void apply(WireBuffer buffer, int textId, String text) {
+            buffer.start(Operations.DATA_TEXT);
+            buffer.writeInt(textId);
+            buffer.writeUTF8(text);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int textId = buffer.readInt();
+
+            String text = buffer.readUTF8(MAX_STRING_SIZE);
+            operations.add(new TextData(textId, text));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.loadText(mTextId, mText);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
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
new file mode 100644
index 0000000..cbe9c12
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+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.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Set a current theme, applied to the following operations in the document.
+ * This can be used to "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 {
+    int mTheme;
+    public static final int UNSPECIFIED = -1;
+    public static final int DARK = -2;
+    public static final int LIGHT = -3;
+
+    public static final Companion COMPANION = new Companion();
+
+    /**
+     * we can then filter operations depending on the chosen theme.
+     *
+     * @param theme the theme we are interested in:
+     *              - Theme.UNSPECIFIED
+     *              - Theme.DARK
+     *              - Theme.LIGHT
+     */
+    public Theme(int theme) {
+        this.mTheme = theme;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTheme);
+    }
+
+    @Override
+    public String toString() {
+        return "SET_THEME " + mTheme;
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.setTheme(mTheme);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "SetTheme";
+        }
+
+        @Override
+        public int id() {
+            return Operations.THEME;
+        }
+
+        public void apply(WireBuffer buffer, int theme) {
+            buffer.start(Operations.THEME);
+            buffer.writeInt(theme);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int theme = buffer.readInt();
+            operations.add(new Theme(theme));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
new file mode 100644
index 0000000..8051ef1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
@@ -0,0 +1,115 @@
+/*
+ * 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.utilities;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class IntMap<T> {
+
+    private static final int DEFAULT_CAPACITY = 16;
+    private static final float LOAD_FACTOR = 0.75f;
+    private static final int NOT_PRESENT = Integer.MIN_VALUE;
+    private int[] mKeys;
+    private ArrayList<T> mValues;
+    int mSize;
+
+    public IntMap() {
+        mKeys = new int[DEFAULT_CAPACITY];
+        Arrays.fill(mKeys, NOT_PRESENT);
+        mValues = new ArrayList<T>(DEFAULT_CAPACITY);
+        for (int i = 0; i < DEFAULT_CAPACITY; i++) {
+            mValues.add(null);
+        }
+    }
+
+    public void clear() {
+        Arrays.fill(mKeys, NOT_PRESENT);
+        mValues.clear();
+        mSize = 0;
+    }
+
+    public T put(int key,  T value)  {
+        if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
+        if (mSize > mKeys.length * LOAD_FACTOR) {
+            resize();
+        }
+        return insert(key, value);
+    }
+
+
+    public  T get(int key) {
+        int index = findKey(key);
+        if (index == -1) {
+            return  null;
+        } else
+            return mValues.get(index);
+    }
+
+    public int size() {
+        return mSize;
+    }
+
+    private  T insert(int key, T value) {
+        int index = hash(key) % mKeys.length;
+        while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
+            index = (index + 1) % mKeys.length;
+        }
+        T oldValue =  null;
+        if (mKeys[index] == NOT_PRESENT) {
+            mSize++;
+        } else {
+            oldValue = mValues.get(index);
+        }
+        mKeys[index] = key;
+        mValues.set(index, value);
+        return oldValue;
+    }
+
+    private  int findKey(int key) {
+        int index = hash(key) % mKeys.length;
+        while (mKeys[index] != NOT_PRESENT) {
+            if (mKeys[index] == key) {
+                return index;
+            }
+            index = (index + 1) % mKeys.length;
+        }
+        return -1;
+    }
+
+    private  int hash(int key) {
+        return key;
+    }
+
+    private   void resize() {
+        int[] oldKeys = mKeys;
+        ArrayList<T> oldValues = mValues;
+        mKeys = new int[(oldKeys.length * 2)];
+        for (int i = 0; i < mKeys.length; i++) {
+            mKeys[i] = NOT_PRESENT;
+        }
+        mValues = new ArrayList<T>(oldKeys.length * 2);
+        for (int i = 0; i < oldKeys.length * 2; i++) {
+            mValues.add(null);
+        }
+        mSize = 0;
+        for (int i = 0; i < oldKeys.length; i++) {
+            if (oldKeys[i] != NOT_PRESENT) {
+                put(oldKeys[i], oldValues.get(i));
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
new file mode 100644
index 0000000..bcda27a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteComposeBuffer;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+import java.io.InputStream;
+
+/**
+ * Public API to create a new RemoteComposeDocument coming from an input stream
+ */
+public class RemoteComposeDocument {
+
+    CoreDocument mDocument = new CoreDocument();
+
+    public RemoteComposeDocument(InputStream inputStream) {
+        RemoteComposeBuffer buffer =
+                RemoteComposeBuffer.fromInputStream(inputStream, mDocument.getRemoteComposeState());
+        mDocument.initFromBuffer(buffer);
+    }
+
+    public CoreDocument getDocument() {
+        return mDocument;
+    }
+
+    public void setDocument(CoreDocument document) {
+        this.mDocument = document;
+    }
+
+    /**
+     * Called when an initialization is needed, allowing the document to eg load
+     * resources / cache them.
+     */
+    public void initializeContext(RemoteContext context) {
+        mDocument.initializeContext(context);
+    }
+
+    /**
+     * Returns the width of the document in pixels
+     */
+    public int getWidth() {
+        return mDocument.getWidth();
+    }
+
+    /**
+     * Returns the height of the document in pixels
+     */
+    public int getHeight() {
+        return mDocument.getHeight();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Painting
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Paint the document
+     *
+     * @param context the provided PaintContext
+     * @param theme   the theme we want to use for this document.
+     */
+    public void paint(RemoteContext context, int theme) {
+        mDocument.paint(context, theme);
+    }
+
+    /**
+     * Returns true if the document can be displayed given this version of the player
+     *
+     * @param majorVersion the max major version supported by the player
+     * @param minorVersion the max minor version supported by the player
+     * @param capabilities a bitmask of capabilities the player supports (unused for now)
+     */
+    public boolean canBeDisplayed(int majorVersion, int minorVersion, long capabilities) {
+        return mDocument.canBeDisplayed(majorVersion, minorVersion, capabilities);
+    }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
new file mode 100644
index 0000000..cc1f3dd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.ScrollView;
+
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
+
+/**
+ * A view to to display and play RemoteCompose documents
+ */
+public class RemoteComposePlayer extends FrameLayout {
+    private RemoteComposeCanvas mInner;
+
+    private static final int MAX_SUPPORTED_MAJOR_VERSION = 0;
+    private static final int MAX_SUPPORTED_MINOR_VERSION = 1;
+
+    public RemoteComposePlayer(Context context) {
+        super(context);
+        init(context, null, 0);
+    }
+
+    public RemoteComposePlayer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs, 0);
+    }
+
+    public RemoteComposePlayer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * Turn on debug information
+     * @param debugFlags 1 to set debug on
+     */
+    public void setDebug(int debugFlags) {
+        if (debugFlags == 1) {
+            mInner.setDebug(true);
+        } else {
+            mInner.setDebug(false);
+        }
+    }
+
+    public void setDocument(RemoteComposeDocument value) {
+        if (value != null) {
+            if (value.canBeDisplayed(
+                    MAX_SUPPORTED_MAJOR_VERSION,
+                    MAX_SUPPORTED_MINOR_VERSION, 0L
+            )
+            ) {
+                mInner.setDocument(value);
+                int contentBehavior = value.getDocument().getContentScroll();
+                applyContentBehavior(contentBehavior);
+            } else {
+                Log.e("RemoteComposePlayer", "Unsupported document ");
+            }
+        } else {
+            mInner.setDocument(null);
+        }
+    }
+
+    /**
+     * Apply the content behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) to the player,
+     * adding or removing scrollviews as needed.
+     *
+     * @param contentBehavior document content behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+     */
+    private void applyContentBehavior(int contentBehavior) {
+        switch (contentBehavior) {
+            case RootContentBehavior.SCROLL_HORIZONTAL: {
+                if (!(mInner.getParent() instanceof HorizontalScrollView)) {
+                    ((ViewGroup) mInner.getParent()).removeView(mInner);
+                    removeAllViews();
+                    LayoutParams layoutParamsInner = new LayoutParams(
+                            LayoutParams.WRAP_CONTENT,
+                            LayoutParams.MATCH_PARENT);
+                    HorizontalScrollView horizontalScrollView =
+                            new HorizontalScrollView(getContext());
+                    horizontalScrollView.setFillViewport(true);
+                    horizontalScrollView.addView(mInner, layoutParamsInner);
+                    LayoutParams layoutParams = new LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.MATCH_PARENT);
+                    addView(horizontalScrollView, layoutParams);
+                }
+            } break;
+            case RootContentBehavior.SCROLL_VERTICAL: {
+                if (!(mInner.getParent() instanceof ScrollView)) {
+                    ((ViewGroup) mInner.getParent()).removeView(mInner);
+                    removeAllViews();
+                    LayoutParams layoutParamsInner = new LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.WRAP_CONTENT);
+                    ScrollView scrollView = new ScrollView(getContext());
+                    scrollView.setFillViewport(true);
+                    scrollView.addView(mInner, layoutParamsInner);
+                    LayoutParams layoutParams = new LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.MATCH_PARENT);
+                    addView(scrollView, layoutParams);
+                }
+            } break;
+            default:
+                if (mInner.getParent() != this)  {
+                    ((ViewGroup) mInner.getParent()).removeView(mInner);
+                    removeAllViews();
+                    LayoutParams layoutParams = new LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.MATCH_PARENT);
+                    addView(mInner, layoutParams);
+                }
+        }
+    }
+
+    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT);
+        mInner = new RemoteComposeCanvas(context, attrs, defStyleAttr);
+        addView(mInner, layoutParams);
+    }
+
+    public interface ClickCallbacks {
+        void click(int id, String metadata);
+    }
+
+    /**
+     * Add a callback for handling click events on the document
+     *
+     * @param callback the callback lambda that will be used when a click is detected
+     *                 <p>
+     *                 The parameter of the callback are:
+     *                 id : the id of the clicked area
+     *                 metadata: a client provided unstructured string associated with that area
+     */
+    public void addClickListener(ClickCallbacks callback) {
+        mInner.addClickListener((id, metadata) -> callback.click(id, metadata));
+    }
+
+    /**
+     * Set the playback theme for the document. This allows to filter operations in order
+     * to have the document adapt to the given theme. This method is intended to be used
+     * to support night/light themes (system or app level), not custom themes.
+     *
+     * @param theme the theme used for playing the document. Possible values for theme are:
+     *              - Theme.UNSPECIFIED -- all instructions in the document will be executed
+     *              - Theme.DARK -- only executed NON Light theme instructions
+     *              - Theme.LIGHT -- only executed NON Dark theme instructions
+     */
+    public void setTheme(int theme) {
+        if (mInner.getTheme() != theme) {
+            mInner.setTheme(theme);
+            mInner.invalidate();
+        }
+    }
+}
+
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
new file mode 100644
index 0000000..3799cf6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -0,0 +1,93 @@
+/*
+ * 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.player.platform;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * An implementation of PaintContext for the Android Canvas.
+ * This is used to play the RemoteCompose operations on Android.
+ */
+public class AndroidPaintContext extends PaintContext {
+    Paint mPaint = new Paint();
+    Canvas mCanvas;
+
+    public AndroidPaintContext(RemoteContext context, Canvas canvas) {
+        super(context);
+        this.mCanvas = canvas;
+    }
+
+    public Canvas getCanvas() {
+        return mCanvas;
+    }
+
+    public void setCanvas(Canvas canvas) {
+        this.mCanvas = canvas;
+    }
+
+    /**
+     * Draw an image onto the canvas
+     *
+     * @param imageId   the id of the image
+     * @param srcLeft   left coordinate of the source area
+     * @param srcTop    top coordinate of the source area
+     * @param srcRight  right coordinate of the source area
+     * @param srcBottom bottom coordinate of the source area
+     * @param dstLeft   left coordinate of the destination area
+     * @param dstTop    top coordinate of the destination area
+     * @param dstRight  right coordinate of the destination area
+     * @param dstBottom bottom coordinate of the destination area
+     */
+
+    @Override
+    public void drawBitmap(int imageId,
+                           int srcLeft,
+                           int srcTop,
+                           int srcRight,
+                           int srcBottom,
+                           int dstLeft,
+                           int dstTop,
+                           int dstRight,
+                           int dstBottom,
+                           int cdId) {
+        AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+        if (androidContext.mRemoteComposeState.containsId(imageId)) {
+            Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(imageId);
+            mCanvas.drawBitmap(
+                    bitmap,
+                    new Rect(srcLeft, srcTop, srcRight, srcBottom),
+                    new Rect(dstLeft, dstTop, dstRight, dstBottom), mPaint
+            );
+        }
+    }
+
+    @Override
+    public void scale(float scaleX, float scaleY) {
+        mCanvas.scale(scaleX, scaleY);
+    }
+
+    @Override
+    public void translate(float translateX, float translateY) {
+        mCanvas.translate(translateX, translateY);
+    }
+}
+
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
new file mode 100644
index 0000000..ce15855
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -0,0 +1,87 @@
+/*
+ * 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.player.platform;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * An implementation of Context for Android.
+ *
+ * This is used to play the RemoteCompose operations on Android.
+ */
+class AndroidRemoteContext extends RemoteContext {
+
+    public void useCanvas(Canvas canvas) {
+        if (mPaintContext == null) {
+            mPaintContext = new AndroidPaintContext(this, canvas);
+        } else {
+            // need to make sure to update the canvas for the current one
+            ((AndroidPaintContext) mPaintContext).setCanvas(canvas);
+        }
+        mWidth = canvas.getWidth();
+        mHeight = canvas.getHeight();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Data handling
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Decode a byte array into an image and cache it using the given imageId
+     *
+     * @oaram imageId the id of the image
+     * @param width with of image to be loaded
+     * @param height height of image to be loaded
+     * @param bitmap a byte array containing the image information
+     */
+    @Override
+    public void loadBitmap(int imageId, int width, int height, byte[] bitmap) {
+        if (!mRemoteComposeState.containsId(imageId)) {
+            Bitmap image = BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length);
+            mRemoteComposeState.cache(imageId, image);
+        }
+    }
+
+    @Override
+    public void loadText(int id, String text) {
+        if (!mRemoteComposeState.containsId(id)) {
+            mRemoteComposeState.cache(id, text);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Click handling
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+
+    @Override
+    public void addClickArea(int id,
+                             int contentDescriptionId,
+                             float left,
+                             float top,
+                             float right,
+                             float bottom,
+                             int metadataId) {
+        String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId);
+        String  metadata = (String) mRemoteComposeState.getFromId(metadataId);
+        mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata);
+    }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
new file mode 100644
index 0000000..672dae3
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player.platform;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.View;
+
+
+/**
+ * Implementation for the click handling
+ */
+class ClickAreaView extends View {
+    private int mId;
+    private String mMetadata;
+    Paint mPaint = new Paint();
+
+    private boolean mDebug;
+
+    ClickAreaView(Context context, boolean debug, int id,
+                         String contentDescription, String metadata) {
+        super(context);
+        this.mId = id;
+        this.mMetadata = metadata;
+        this.mDebug = debug;
+        setContentDescription(contentDescription);
+    }
+
+
+    public void setDebug(boolean value) {
+        if (mDebug != value) {
+            mDebug = value;
+            invalidate();
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mDebug) {
+            mPaint.setARGB(200, 200, 0, 0);
+            mPaint.setStrokeWidth(3f);
+            canvas.drawLine(0, 0, getWidth(), 0, mPaint);
+            canvas.drawLine(getWidth(), 0, getWidth(), getHeight(), mPaint);
+            canvas.drawLine(getWidth(), getHeight(), 0, getHeight(), mPaint);
+            canvas.drawLine(0, getHeight(), 0, 0, mPaint);
+
+            mPaint.setTextSize(20f);
+            canvas.drawText("id: " + mId + " : " + mMetadata, 4, 22, mPaint);
+        }
+    }
+}
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
new file mode 100644
index 0000000..a3bb73e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -0,0 +1,230 @@
+/*
+ * 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.player.platform;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
+
+import java.util.Set;
+
+/**
+ * Internal view handling the actual painting / interactions
+ */
+public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachStateChangeListener {
+
+    static final boolean USE_VIEW_AREA_CLICK = true; // Use views to represent click areas
+    RemoteComposeDocument mDocument = null;
+    int mTheme = Theme.LIGHT;
+    boolean mInActionDown = false;
+    boolean mDebug = false;
+    Point mActionDownPoint = new Point(0, 0);
+
+    public RemoteComposeCanvas(Context context) {
+        super(context);
+        if (USE_VIEW_AREA_CLICK) {
+            addOnAttachStateChangeListener(this);
+        }
+    }
+
+    public RemoteComposeCanvas(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        if (USE_VIEW_AREA_CLICK) {
+            addOnAttachStateChangeListener(this);
+        }
+    }
+
+    public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setBackgroundColor(Color.WHITE);
+        if (USE_VIEW_AREA_CLICK) {
+            addOnAttachStateChangeListener(this);
+        }
+    }
+
+    public void setDebug(boolean value) {
+        if (mDebug != value) {
+            mDebug = value;
+            if (USE_VIEW_AREA_CLICK) {
+                for (int i = 0; i < getChildCount(); i++) {
+                    View child = getChildAt(i);
+                    if (child instanceof ClickAreaView) {
+                        ((ClickAreaView) child).setDebug(mDebug);
+                    }
+                }
+            }
+            invalidate();
+        }
+    }
+
+    public void setDocument(RemoteComposeDocument value) {
+        mDocument = value;
+        mDocument.initializeContext(mARContext);
+        setContentDescription(mDocument.getDocument().getContentDescription());
+        requestLayout();
+    }
+
+    AndroidRemoteContext mARContext = new AndroidRemoteContext();
+
+    @Override
+    public void onViewAttachedToWindow(View view) {
+        if (mDocument == null) {
+            return;
+        }
+        Set<CoreDocument.ClickAreaRepresentation> clickAreas = mDocument
+                .getDocument().getClickAreas();
+        removeAllViews();
+        for (CoreDocument.ClickAreaRepresentation area : clickAreas) {
+            ClickAreaView viewArea = new ClickAreaView(getContext(), mDebug,
+                    area.getId(), area.getContentDescription(),
+                    area.getMetadata());
+            int w = (int) area.width();
+            int h = (int) area.height();
+            FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(w, h);
+            param.width = w;
+            param.height = h;
+            param.leftMargin = (int) area.getLeft();
+            param.topMargin = (int) area.getTop();
+            viewArea.setOnClickListener(view1
+                    -> mDocument.getDocument().performClick(area.getId()));
+            addView(viewArea, param);
+        }
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View view) {
+        removeAllViews();
+    }
+
+
+    public  interface ClickCallbacks {
+        void click(int id, String metadata);
+    }
+
+    public void addClickListener(ClickCallbacks callback) {
+        if (mDocument == null) {
+            return;
+        }
+        mDocument.getDocument().addClickListener((id, metadata) -> callback.click(id, metadata));
+    }
+
+    public int getTheme() {
+        return mTheme;
+    }
+
+    public void setTheme(int theme) {
+        this.mTheme = theme;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        if (USE_VIEW_AREA_CLICK) {
+            return super.onTouchEvent(event);
+        }
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mActionDownPoint.x = (int) event.getX();
+                mActionDownPoint.y = (int) event.getY();
+                mInActionDown = true;
+                return true;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                mInActionDown = false;
+                return true;
+            }
+            case MotionEvent.ACTION_UP: {
+                mInActionDown = false;
+                performClick();
+                return true;
+            }
+            case MotionEvent.ACTION_MOVE: {
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (USE_VIEW_AREA_CLICK) {
+            return super.performClick();
+        }
+        mDocument.getDocument().onClick((float) mActionDownPoint.x, (float) mActionDownPoint.y);
+        super.performClick();
+        return true;
+    }
+
+    public int measureDimension(int measureSpec, int intrinsicSize) {
+        int result = intrinsicSize;
+        int mode = MeasureSpec.getMode(measureSpec);
+        int size = MeasureSpec.getSize(measureSpec);
+        switch (mode) {
+            case MeasureSpec.EXACTLY:
+                result = size;
+                break;
+            case MeasureSpec.AT_MOST:
+                result = Integer.min(size, intrinsicSize);
+                break;
+            case MeasureSpec.UNSPECIFIED:
+                result = intrinsicSize;
+        }
+        return result;
+    }
+
+    private static final float[] sScaleOutput = new float[2];
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mDocument == null) {
+            return;
+        }
+        int w = measureDimension(widthMeasureSpec, mDocument.getWidth());
+        int h = measureDimension(heightMeasureSpec, mDocument.getHeight());
+
+        if (!USE_VIEW_AREA_CLICK) {
+            if (mDocument.getDocument().getContentSizing() == RootContentBehavior.SIZING_SCALE) {
+                mDocument.getDocument().computeScale(w, h, sScaleOutput);
+                w = (int) (mDocument.getWidth() * sScaleOutput[0]);
+                h = (int) (mDocument.getHeight() * sScaleOutput[1]);
+            }
+        }
+        setMeasuredDimension(w, h);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mDocument == null) {
+            return;
+        }
+        mARContext.setDebug(mDebug);
+        mARContext.useCanvas(canvas);
+        mARContext.mWidth = getWidth();
+        mARContext.mHeight = getHeight();
+        mDocument.paint(mARContext, mTheme);
+    }
+
+}
+
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index adb0c69..096f246 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -273,6 +273,13 @@
     String getRestrictedAccountType();
 
     /**
+     * @see R.styleable#AndroidManifestApplication_emergencyInstaller
+     * @hide
+     */
+    @Nullable
+    String getEmergencyInstaller();
+
+    /**
      * @see ApplicationInfo#roundIconRes
      * @see R.styleable#AndroidManifestApplication_roundIcon
      */
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ae23942..bed7768 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -75,6 +75,7 @@
     jfieldID windowToken;
     jfieldID focusTransferTarget;
     jfieldID alpha;
+    jfieldID canOccludePresentation;
 } gInputWindowHandleClassInfo;
 
 static struct {
@@ -327,6 +328,8 @@
                         javaObjectForIBinder(env, windowInfo.windowToken));
 
     env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.alpha, windowInfo.alpha);
+    env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.canOccludePresentation,
+                         windowInfo.canOccludePresentation);
 
     return inputWindowHandle;
 }
@@ -451,6 +454,9 @@
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.alpha, clazz, "alpha", "F");
 
+    GET_FIELD_ID(gInputWindowHandleClassInfo.canOccludePresentation, clazz,
+                 "canOccludePresentation", "Z");
+
     jclass surfaceControlClazz;
     FIND_CLASS(surfaceControlClazz, "android/view/SurfaceControl");
     GET_FIELD_ID(gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject,
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 2f29cae..917d283 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/android_opengl_EGL15.cpp b/core/jni/android_opengl_EGL15.cpp
index b9c36b9..447b8ec 100644
--- a/core/jni/android_opengl_EGL15.cpp
+++ b/core/jni/android_opengl_EGL15.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/android_opengl_EGLExt.cpp b/core/jni/android_opengl_EGLExt.cpp
index cdc9852..ffd75ea 100644
--- a/core/jni/android_opengl_EGLExt.cpp
+++ b/core/jni/android_opengl_EGLExt.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
@@ -54,11 +55,11 @@
     jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
     eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
     jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
-    eglsyncClass = (jclass)_env->NewGlobalRef(eglsyncClassLocal);
+    eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
 
     egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
     eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
-    eglsyncGetHandleID = _env->GetMethodID(eglsyncClassLocal, "getNativeHandle", "()J");
+    eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
 }
 
 static void *
@@ -72,6 +73,14 @@
     return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
 }
 
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
+
+    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
+}
+
 // --------------------------------------------------------------------------
 /* EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time ) */
 static jboolean
@@ -89,21 +98,12 @@
     return (jboolean)_returnValue;
 }
 
-static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
-    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
-    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
-
-    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
-}
-
 static const char *classPathName = "android/opengl/EGLExt";
 
 static const JNINativeMethod methods[] = {
-        {"_nativeClassInit", "()V", (void *)nativeClassInit},
-        {"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z",
-         (void *)android_eglPresentationTimeANDROID},
-        {"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I",
-         (void *)android_eglDupNativeFenceFDANDROID},
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z", (void *) android_eglPresentationTimeANDROID },
+{"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I", (void *)android_eglDupNativeFenceFDANDROID },
 };
 
 int register_android_opengl_jni_EGLExt(JNIEnv *_env)
diff --git a/core/jni/android_opengl_GLES10.cpp b/core/jni/android_opengl_GLES10.cpp
index d65b498..2d921ad 100644
--- a/core/jni/android_opengl_GLES10.cpp
+++ b/core/jni/android_opengl_GLES10.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES10Ext.cpp b/core/jni/android_opengl_GLES10Ext.cpp
index 3638b87..35a9a68 100644
--- a/core/jni/android_opengl_GLES10Ext.cpp
+++ b/core/jni/android_opengl_GLES10Ext.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 9724e6c..e04b56e 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index 1ffa4ec..bccbda6 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index d832558..165262e 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES2/gl2.h>
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 719c6b3..d3fe439 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl3.h>
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index afe7c63..b123f9d 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 8127433..1e4049b 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl31.h>
diff --git a/core/jni/android_opengl_GLES32.cpp b/core/jni/android_opengl_GLES32.cpp
index 7ed7548..e0175f0 100644
--- a/core/jni/android_opengl_GLES32.cpp
+++ b/core/jni/android_opengl_GLES32.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 239c626..aae0da9 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -14,17 +14,16 @@
  * limitations under the License.
  */
 
-#include <input/Input.h>
+#include "android_view_InputDevice.h"
 
 #include <android_runtime/AndroidRuntime.h>
+#include <com_android_input_flags.h>
+#include <input/Input.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
-
 #include <nativehelper/ScopedLocalRef.h>
 
-#include "android_view_InputDevice.h"
 #include "android_view_KeyCharacterMap.h"
-
 #include "core_jni_helpers.h"
 
 namespace android {
@@ -34,6 +33,7 @@
 
     jmethodID ctor;
     jmethodID addMotionRange;
+    jmethodID setShouldSmoothScroll;
 } gInputDeviceClassInfo;
 
 jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo) {
@@ -103,6 +103,18 @@
         }
     }
 
+    if (com::android::input::flags::input_device_view_behavior_api()) {
+        const InputDeviceViewBehavior& viewBehavior = deviceInfo.getViewBehavior();
+        std::optional<bool> defaultSmoothScroll = viewBehavior.shouldSmoothScroll;
+        if (defaultSmoothScroll.has_value()) {
+            env->CallVoidMethod(inputDeviceObj.get(), gInputDeviceClassInfo.setShouldSmoothScroll,
+                                *defaultSmoothScroll);
+            if (env->ExceptionCheck()) {
+                return NULL;
+            }
+        }
+    }
+
     return env->NewLocalRef(inputDeviceObj.get());
 }
 
@@ -118,6 +130,8 @@
 
     gInputDeviceClassInfo.addMotionRange =
             GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
+    gInputDeviceClassInfo.setShouldSmoothScroll =
+            GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "setShouldSmoothScroll", "(Z)V");
     return 0;
 }
 
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 323f7b6..b5fbb22 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -73,7 +73,7 @@
     uint32_t mNextPublishedSeq;
 
     const std::string getInputChannelName() {
-        return mInputPublisher.getChannel()->getName();
+        return mInputPublisher.getChannel().getName();
     }
 
     int handleEvent(int receiveFd, int events, void* data) override;
@@ -102,7 +102,7 @@
 }
 
 status_t NativeInputEventSender::initialize() {
-    const int receiveFd = mInputPublisher.getChannel()->getFd();
+    const int receiveFd = mInputPublisher.getChannel().getFd();
     mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
     return OK;
 }
@@ -112,7 +112,7 @@
         LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
     }
 
-    mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+    mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel().getFd());
 }
 
 status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7e325a5..58166bf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -667,8 +667,9 @@
   }
 }
 
-static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn, jlong bounding_capabilities) {
   for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+    if ((1LL << i) & bounding_capabilities) continue;
     if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
       if (errno == EINVAL) {
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
@@ -680,6 +681,27 @@
   }
 }
 
+static bool MatchGid(JNIEnv* env, jintArray gids, jint gid, jint gid_to_find) {
+  if (gid == gid_to_find) return true;
+
+  if (gids == nullptr) return false;
+
+  jsize gids_num = env->GetArrayLength(gids);
+  ScopedIntArrayRO native_gid_proxy(env, gids);
+
+  if (native_gid_proxy.get() == nullptr) {
+    RuntimeAbort(env, __LINE__, "Bad gids array");
+  }
+
+  for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
+    if (native_gid_proxy[gids_index] == gid_to_find) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
@@ -1875,9 +1897,9 @@
 // Utility routine to specialize a zygote child process.
 static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
                              jobjectArray rlimits, jlong permitted_capabilities,
-                             jlong effective_capabilities, jint mount_external,
-                             jstring managed_se_info, jstring managed_nice_name,
-                             bool is_system_server, bool is_child_zygote,
+                             jlong effective_capabilities, jlong bounding_capabilities,
+                             jint mount_external, jstring managed_se_info,
+                             jstring managed_nice_name, bool is_system_server, bool is_child_zygote,
                              jstring managed_instruction_set, jstring managed_app_data_dir,
                              bool is_top_app, jobjectArray pkg_data_info_list,
                              jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
@@ -1891,6 +1913,9 @@
     auto instruction_set = extract_fn(managed_instruction_set);
     auto app_data_dir = extract_fn(managed_app_data_dir);
 
+    // Permit bounding capabilities
+    permitted_capabilities |= bounding_capabilities;
+
     // Keep capabilities across UID change, unless we're staying root.
     if (uid != 0) {
         EnableKeepCapabilities(fail_fn);
@@ -1898,7 +1923,7 @@
 
     SetInheritable(permitted_capabilities, fail_fn);
 
-    DropCapabilitiesBoundingSet(fail_fn);
+    DropCapabilitiesBoundingSet(fail_fn, bounding_capabilities);
 
     bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() &&
             android::NativeBridgeAvailable() &&
@@ -2165,6 +2190,23 @@
     return capdata[0].effective | (static_cast<uint64_t>(capdata[1].effective) << 32);
 }
 
+static jlong CalculateBoundingCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids) {
+    jlong capabilities = 0;
+
+    /*
+     * Grant CAP_SYS_NICE to CapInh/CapPrm/CapBnd for processes that can spawn
+     * VMs.  This enables processes to execve on binaries with elevated
+     * capabilities if its file capability bits are set. This does not grant
+     * capability to the parent process(that spawns the VM) as the effective
+     * bits are not set.
+     */
+    if (MatchGid(env, gids, gid, AID_VIRTUALMACHINE)) {
+        capabilities |= (1LL << CAP_SYS_NICE);
+    }
+
+    return capabilities;
+}
+
 static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids,
                                    bool is_child_zygote) {
   jlong capabilities = 0;
@@ -2198,26 +2240,7 @@
    * Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
    */
 
-  bool gid_wakelock_found = false;
-  if (gid == AID_WAKELOCK) {
-    gid_wakelock_found = true;
-  } else if (gids != nullptr) {
-    jsize gids_num = env->GetArrayLength(gids);
-    ScopedIntArrayRO native_gid_proxy(env, gids);
-
-    if (native_gid_proxy.get() == nullptr) {
-      RuntimeAbort(env, __LINE__, "Bad gids array");
-    }
-
-    for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
-      if (native_gid_proxy[gids_index] == AID_WAKELOCK) {
-        gid_wakelock_found = true;
-        break;
-      }
-    }
-  }
-
-  if (gid_wakelock_found) {
+  if (MatchGid(env, gids, gid, AID_WAKELOCK)) {
     capabilities |= (1LL << CAP_BLOCK_SUSPEND);
   }
 
@@ -2494,6 +2517,7 @@
         jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
         jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+    jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
       zygote::ZygoteFailure(env, "zygote", nice_name,
@@ -2532,10 +2556,11 @@
 
     if (pid == 0) {
         SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
-                         mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
-                         instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
-                         allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
-                         mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+                         bounding_capabilities, mount_external, se_info, nice_name, false,
+                         is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                         is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+                         mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+                         mount_sysprop_overrides == JNI_TRUE);
     }
     return pid;
 }
@@ -2568,7 +2593,7 @@
       // System server prcoess does not need data isolation so no need to
       // know pkg_data_info_list.
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
-                       effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
+                       effective_capabilities, 0, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                        false, nullptr, nullptr, /* is_top_app= */ false,
                        /* pkg_data_info_list */ nullptr,
                        /* allowlisted_data_info_list */ nullptr, false, false, false);
@@ -2725,12 +2750,14 @@
         jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
         jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+    jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
 
     SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
-                     mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
-                     instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
-                     allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
-                     mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+                     bounding_capabilities, mount_external, se_info, nice_name, false,
+                     is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                     is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+                     mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+                     mount_sysprop_overrides == JNI_TRUE);
 }
 
 /**
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 21de723..ef29c88 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 381580b..c65794e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -19,6 +19,7 @@
 per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
 per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
 per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
+per-file android/hardware/location/context_hub_info.proto = file:/services/core/java/com/android/server/location/contexthub/OWNERS
 
 # Biometrics
 jaggies@google.com
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index d3f3af7..2861858 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -723,6 +723,7 @@
         // encoded as a key=value list separated by commas.
         optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto disable_screen_share_protections_for_apps_and_notifications = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Notification notification = 82;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0171f58..c71a842 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -881,6 +881,19 @@
         android:description="@string/permdesc_writeContacts"
       android:protectionLevel="dangerous" />
 
+    <!-- Allows an app to update the verification status of E2EE contact keys owned by other apps.
+        <p>This permission is only granted to system apps.
+        <p>Protection level: signature|privileged
+        @SystemApi
+        @hide
+        @FlaggedApi("android.provider.user_keys")
+    -->
+    <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+                android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to set default account for new contacts.
         <p> This permission is only granted to system applications fulfilling the Contacts app role.
         <p>Protection level: internal|role
@@ -2632,6 +2645,13 @@
                 android:description="@string/permdesc_detectScreenCapture"
                 android:protectionLevel="normal" />
 
+    <!-- Allows an application to get notified when it is being recorded.
+         <p>Protection level: normal
+         @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+    -->
+    <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+                android:protectionLevel="normal" />
+
     <!-- ======================================== -->
     <!-- Permissions for factory reset protection -->
     <!-- ======================================== -->
@@ -3188,6 +3208,14 @@
     <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
         android:protectionLevel="signature|appop" />
 
+    <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+     <p>Protection level: normal
+     @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+    <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+        android:label="@string/permlab_accessHiddenProfile"
+        android:description="@string/permdesc_accessHiddenProfile"
+        android:protectionLevel="normal" />
+
     <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
     <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
                 android:protectionLevel="signature|role" />
@@ -5563,6 +5591,7 @@
          of a session based install.
          <p>Not for use by third-party applications.
          @hide
+         @FlaggedApi("android.content.pm.get_resolved_apk_path")
     -->
     <permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS"
                 android:protectionLevel="signature|installer" />
@@ -7828,7 +7857,7 @@
                 android:protectionLevel="normal"/>
 
     <!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
-         Gives applications whose <b>primary use case</b> is to backup or sync content increased
+         Gives applications with a <b>major use case</b> of backing-up or syncing content increased
          job execution allowance in order to complete the related work. The jobs must have a valid
          content URI trigger and network constraint set.
          <p>This is a special access permission that can be revoked by the system or the user.
diff --git a/core/res/assets/geoid_height_map/README.md b/core/res/assets/geoid_height_map/README.md
deleted file mode 100644
index 849d32e..0000000
--- a/core/res/assets/geoid_height_map/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-These binary protos are generated at runtime from the text protos in ../../geoid_height_map_assets
-and using aprotoc.
\ No newline at end of file
diff --git a/core/res/assets/geoid_map/README.md b/core/res/assets/geoid_map/README.md
new file mode 100644
index 0000000..5d480c1
--- /dev/null
+++ b/core/res/assets/geoid_map/README.md
@@ -0,0 +1,2 @@
+These binary protos are generated at runtime from the text protos in ../../geoid_map_assets and
+using aprotoc.
\ No newline at end of file
diff --git a/core/res/assets/geoid_map/expiration-distance-disk-tile-1.pb b/core/res/assets/geoid_map/expiration-distance-disk-tile-1.pb
new file mode 100644
index 0000000..c2ec5d9
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-disk-tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_map/expiration-distance-disk-tile-3.pb b/core/res/assets/geoid_map/expiration-distance-disk-tile-3.pb
new file mode 100644
index 0000000..e4b3b0c
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-disk-tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_map/expiration-distance-disk-tile-5.pb b/core/res/assets/geoid_map/expiration-distance-disk-tile-5.pb
new file mode 100644
index 0000000..95f54c2
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-disk-tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_map/expiration-distance-disk-tile-7.pb b/core/res/assets/geoid_map/expiration-distance-disk-tile-7.pb
new file mode 100644
index 0000000..896bbc1
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-disk-tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_map/expiration-distance-disk-tile-9.pb b/core/res/assets/geoid_map/expiration-distance-disk-tile-9.pb
new file mode 100644
index 0000000..ddc687d
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-disk-tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_map/expiration-distance-disk-tile-b.pb b/core/res/assets/geoid_map/expiration-distance-disk-tile-b.pb
new file mode 100644
index 0000000..eb405f6
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-disk-tile-b.pb
Binary files differ
diff --git a/core/res/assets/geoid_map/expiration-distance-params.pb b/core/res/assets/geoid_map/expiration-distance-params.pb
new file mode 100644
index 0000000..e160120
--- /dev/null
+++ b/core/res/assets/geoid_map/expiration-distance-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_map/geoid-height-disk-tile-1.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/tile-1.pb
rename to core/res/assets/geoid_map/geoid-height-disk-tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_map/geoid-height-disk-tile-3.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/tile-3.pb
rename to core/res/assets/geoid_map/geoid-height-disk-tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_map/geoid-height-disk-tile-5.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/tile-5.pb
rename to core/res/assets/geoid_map/geoid-height-disk-tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_map/geoid-height-disk-tile-7.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/tile-7.pb
rename to core/res/assets/geoid_map/geoid-height-disk-tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_map/geoid-height-disk-tile-9.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/tile-9.pb
rename to core/res/assets/geoid_map/geoid-height-disk-tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_map/geoid-height-disk-tile-b.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/tile-b.pb
rename to core/res/assets/geoid_map/geoid-height-disk-tile-b.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_map/geoid-height-params.pb
similarity index 100%
rename from core/res/assets/geoid_height_map/map-params.pb
rename to core/res/assets/geoid_map/geoid-height-params.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_map_assets/README.md
similarity index 100%
rename from core/res/geoid_height_map_assets/README.md
rename to core/res/geoid_map_assets/README.md
diff --git a/core/res/geoid_map_assets/expiration-distance-disk-tile-1.textpb b/core/res/geoid_map_assets/expiration-distance-disk-tile-1.textpb
new file mode 100644
index 0000000..a4170e6
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-disk-tile-1.textpb
@@ -0,0 +1,2 @@
+tile_key: "1"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000@\000\000\000@\b\000\000\000\000\217\002.\002\000\000\v\361IDATx^\035WYo\034Ir\256\256\252\274\357\314:\262\316\276\233\315S$E\211\244\244\341H\262$\256V\243\3359W\322\314\216\027Z\f\026\006\326\360\313b\001\277\032~1\f\333\257\376\t\376\233\216\232\006\371\322\310\214\216\214\370\342\373\276H^~\367\037\377\365\343\325\361\320\307\252\352\207\276\237o\327]w~zr~\262\026Xe\271\t\224*\257\352\3233\253\257\317\317W\265\337\305e\325\354\272X9k\271L\256_\376\353\377~z\264\031\372\246\254\272E\333\265\355vX\274y\376\360\226s\225\347i\232et?r\226#\032\312\217\377p\273)|\027\027\355\3204}\033\242\310E\322\275\372\374\267g\347\273\305\242\201\200E\263\034\347\235v\217\317\037.\373\276\330\342\024\"\220\343\315|\254,\301t}\376\207C\347\\\025\302f\fq\331\264\002\311$^\235\277\177|u\260j\272\262\366E]\327\373m=\177y\276\"\234 \256\226.\307\354v=\216\273\001\347DJiL\251\255\032\206\255\026m\355\030\226\311\345\343\375\301uU\314\245d\314\225\363\272\033\026\266\352{.p\236\271j\377\305\273\273\273\367\317\317v\333M\236s\257\363\034\033]\354[c\004\215\215\"9M\212b\277\252\215\366%Ms\241\275[\217\205=\350\021\211\224z\234\243\347w\217\317\316\036\270\233\'\373\363\262\222\230\"\312\225\365\255\022\234\370\261T\210&\262\210ma\031!\2311U\260\306\327\205\320\273\262\334P\302Q\216\324\227\257ONN\355\356\365\333\037\357;iJ\2458\227\\\325N\223\320\365\216\242\204\037VJ2\244T\325\325c\335-J\277h\254\332=;\333s\212\020!\264\177\365\366\313\353G_\276\376\366FR*\030\306\224b\214KK\nhD\215\022RW\222Jf\v\353\252]U\257v\273\335v.\352\373o\276~F\363\034!\210\342N\256\037=\277\270Xs\316\021\245\324\300W\310\030B\353\266\245y\302\245\307H.\252J\n3T\325\260^\257\016O\255n?\377r9\245\200q\236\273\3475\272|x\244\324\004\251\002\242d\024\v\217\3110o\263<!\232\321~uRE@\201\017\313\305f\263Z\227\326\276\372\373\377\374\346\351r\036%\244\301\337\f\362\346\341R(\316\nk\332\016\nF\030\000t\230w9K\000\037\230-\240\227\'CQ\270\242k\206\361h\241\324\307\377\373\317\237\337|\361t_j\226\323\313\363w\357\026\255n\2056\206\005G1\200CJ\347\232\np \210a\f\253\027w\267\217\226M\b}\323l\326\313\260\375\360O\277|\377\273\267O\216N\217;D\212\352\315\317sc\234\320\256\f\212\301\313x\200\000\b\033B \000\315YF]\374\355\267\227\243\325\306\205aQ\371\360\325\307\367/\256\276\370\342v3\366\232\241\374\372\273^k\313\353UQ*\306\b\346^J\236cII\022\004C8KS\230\226\243\273y\f\316\373\022\336\362\325\217\367O\316\326C\327-\326\253R\220\366\266S\232[\331\350\252l\265\244\\HB\255\343B%\326\230\250R\n\030#\b\337n\327\313U\277,\030\343\337\274{\275\335U]\327\036,\312\202\242S\350\222\206\264\213X;\3079F\n\263Fc\302\222\312\2320\r\335\315\315WU\375\351d\273\202\000}d\354\247\177\377\343\315~5^\235mG\2559UU\201\275Q\316\311\340\205\224yn9a\022\347i\262\232j\230e\352\305\375\037\276\377\360\303\3610\214\355\342\240\256\207\357\376\355\277_\234_\337=~r\fE\200\251\320V;\003\237\322a\")\312\t\003@`F\222BC\000\024\353\243W\177\375\376\307\'\253\266\353\233\345B\252\323g\227\177\377\370\364\370\260\276zy\266\\\356e\216\204\016ee]\343\001\2370w\204\331\n0\226%\232\023\301\371\372\240\264\277\375\353\037\317\226]\273\030\227\203\224\2671\036\331\agg\333\353O\357.\317\317\216\f\343&l\233PuNA\301`\256\265s\204\247i\242\005Q\212\001\312\252\235x\362hy8\366m\003Q\236\235X\234\271p\377\315\317\037\177\370\352\351\305\221\221\210\273v4f\t\224D\020t\016\273\300h\n\031(\350N\016\300\242\356\360\351\243\343]?\f\233\332\356\277y\342Lk\364\376\376\273\037~\372\363\207\017\227\000x\331\372B\312\256*\203\311Q\216I\027\020\274#\341\212k\231+\026\335\321\347_\316\317\017\026\243\2130K\217\237/\004\242\230l\337\376\360\347\337\377\356\356\346\264f\332\020m\255\365Ji\201S\344\"B\314W\211\022\006Per67\027\257n6c\315\3750D\021\027\341\031eK\202\312\373\237_==\336\f\212rBli\005gB(\234f\250@\244\036\253$X)\006S d\271zv\270\352-\005}\210\315\260\360\',\000\027\347\375O\257\237\356\227\205\207\031\240\2057BcLr`\373\214\bQ\214CR\303\273\204\26087kwz8\326\3327\274!j\275\334_\\?\210\221g\370\371\277\274\277\3314^B\000\243\225\344\224\300m\v\275\307\322\037\306\244,\225T\312\020\276\235o\340~\251t\031\243c\276\256\216\257_\275X\223<\257\257\276\275\177\030\f\a|s8\313\215\247\316\002\236\201\360\344|\225h\v\365Uy\206\351|\263\265]\3356M\354\326>\324E\r\2742\027@J2\336\003-0-!\202p\241A\310\353\236\003;2\336m\022ea<s\224ehy\260[\217UW5\303r\345\233\373\233\365`UW\032\t\334\265P@xAs\030(UD\240\264\302\243,\205b\2641a\224\021\224Swp\262Z\265Pc.\250\250\273z\371\351\353\177\376\374\217\237o\035\345p\016\006\330h#\245\226\302\215pW\351\036\000\r\331\371\022\002PH`:B!\213,\233\002`&n\277\376\345\303\357\377\364\351\315\233\301\v`q\216\271\001\366\347J\355;\21105!\317\250\226\210T\t\002zq\276m\v\a\332\006\001r\370c\f\b\357\376\371\335\207\277\275\177s$\303\031\312\004\234\221\202\251r\034Z#2n\f(\237\362\004\253\004M}\005\232b\224P_\272\240\200\236r\220\016\346\317\216\037\335\\\375\345\323\361\303\337\344)\343\032\002pS\017CiU\232\351<C9\267\001\243D\224\205\026\206K\355\005k+]\233)\006Dc\246l\226\'Ww\037\236\235\035\027\0316\023\004\204T^k]\344\251\230\332\306\214\205\000\276\357u,CQxyr~tq\321UUY(\023\255\244\312r\036\016\017j\306L\312\314\257\252\b\315\320a\250\2630=X\206\322\362d\021\035\020\245\v\222\210\376d\267]\305xryr\2727\030\224\025\230\a\2031\341\302f)e\034\304\031h\251\177\262h\025\241\360h\306}\341\023\005<Y\026\336{1?\326\240\032\321\025\017\256\326\f\223\246\206\252\023\002\322\206\244\225\030[\a\327\234/\264\367]e%\313\031(\302\302\'@T\021<\213Sj\354\r\364\220\270\366\270\202\206:)\232\346\242\001\n\205\237\016s\201\tUF\031\b`l\347z\r\336\201a\352}R\324\261\264zRmi\254\323\032$\234\242\f\340k\325\341\246\357\275o\036\200\376P2M\214\323!\024\306\354\306jY\021\202\322\224)\233\204\222\302\\\201\356V6\314\227\313\000\272\207\177\305\277sc\177pX\201_hAL\t\b\216\205Y\016U\335\306\020j\270\003\307\250\220@i\224 \230\275\252j\232!\004\353\n\016=\243f\330-\373.\356\367c\250|\\\fu\214!\026N\311\020\274\325\241 D\020`$\316 \300\204P@\272\203\361+\003\0302\231;\307t\327\035=]tm\035\373\"\366\273\323\355\252\033\326\261\356\242\322p\243\360\226L\211\303x&`B0\017\261\254\212\002\322h\332\345\240\362**\277\a\273V\v \301f\230\257\267\333\261,\227\307\203\265Q\262\322\201v\226\r$\316\255\222\tB\200\242\276.\n\253\224\367\263\031\352\025\251[\356\273q\230\357\025\030\002kO\327\213yl\332\341\374\240.\332\2713UU\270\261\342\031\210\023\322\t\341\215\373\265@Pd/\263Y\006\001\000tE7\276\275:X\037\201+\260\363\323\226\272P\327M,\233E\337\2128\200D\367,\233l\006\001\233W\200\244S\351\255\247U\226fxs\004-\221\252x\360\362\305\227 \254\240\030E\327\022k\353\246\261\266\005\301\232\017]]\373\025\320\020\206\366\'\n\222\a\315\005(r\3329\273Z_J*\240\030\253f\274\273*K\306J\003\364&Z0p0\250b\263:\214q\\T\332\026$+{\312\223\326;\037@\256\234\t\213\365|9\237_p*c\324u3\202?(\313\342`\tz\016\361\243\3078#\355a\023\233n\327T5\340\260\251\021N`<\240\002\360q\325f\034\227\3077\003\307Z\201g+\333\"n\273\262\b\001\b\325KW\031msjw24GkW\004\230\324&\315I\002\371\033\353\'s\324\235\354\346\353\265\303\036\340\021\240\253\315<\266uo\200\'\v\3707m\031F-MW\024m\333\307\326\001\364iJp\342\nm\001\3406T\363#\350\367\246A9 \3054\020\266o+\230\327((\210\231\\\215\253\312\205\t\207e_\307C\017_\211,c`\266\341\370\364\000\200FW\223f\025\201\243g\251lm\230Z\v\302Y\273\336\272ry\321\3250\036\301\273X\265 \333\225r\255\206\005\202\'\240k\022\350Jy\230\220\272\356\373\276Ig\263Y\306&\036\000/\006\303\\m\316\206\330\\,\232X\204\241/\344<\364\026s\006b\006\236\217\'\002\370\006\234\2323\316\227\343|9\324M\232\242,\237\364s\n\000\203\"T7V\256l\343\256\f][J_\0160\377 G,\002\235&R\201W\361Jh\355\2624\036\rCU\v\020#\006\002\nH\003s\016Dh\333\302\000Zb\333\004 K1\275-\a\006\200{Z\'\030\224\325Z L\256\263\324\271\266Wt\006\213\226I\'\225\311\375D\370\004\244E\256\226e\321F\340BHO\030\224\247\324.\n+mBA\260\004\237\350\306\000\221I\v\030\303<\313R\002{\005X\224T\t\006\313\037\202\031t\214)\224O\231\375\332\001\002\364\315\031O\200\235\341G\024\224Q\346`\031\246ta\337B\263\351 \254X \273\323\035\316 PJ\300\241\202\'\245B\030\223\206iR\201\017&\021\202{\234Y\0209PZx\266\244\031\232a\340c\f\244CA\375a\371,5\312f\222\344\320\241Y*`G@\\O\223j\022\006\v\"T\032\312\n\002\2073\304\b\2255\005\357\312*\247\240Z\006\030\276\346\024{\221\315R\232\315f\371,\231eS\247\241w\300N\t\345\034\004\036\274#\370\357<\317\230\201j\002e\202t\201;e\006\222\2438\202|\002\315\247\263\3515\f\315\222d\006/\341\240\367@\252\223\302Lp\221\300\274\210p\300%`\317Z\006\313\200\001#\bN\217\360\250\263\334\341i\a\313&\271\302\2239O\341q\23239\001\t~E\022\246\b\330\377XX\320/\251\'\242\304\320G:\351\axs\020\032\260e\be\b\002z\230\2114\005\356\206\276\210dB\233\202E\024\224O\214\233\276\204\212h\312 \327\251\\\031\355\300u\n\232M\276\016\201\'\310\t&\260n\321\276\201&C\3219\375\177\264\213Dt\003\247\373\213\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_map_assets/expiration-distance-disk-tile-3.textpb b/core/res/geoid_map_assets/expiration-distance-disk-tile-3.textpb
new file mode 100644
index 0000000..684f848
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-disk-tile-3.textpb
@@ -0,0 +1,2 @@
+tile_key: "3"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000@\000\000\000@\b\000\000\000\000\217\002.\002\000\000\nKIDATx^-\227\351r\334\326\222\204\321\000\316\276c\355\225-\262%Q\242e[\226=\023s\035w\376\315<\302\274\377\253\314W\224\021\022\311\350\006\352\324\222\231\225\350.9\2720\244\266?=\335?\377\366\353\257\277?\266\323\375\345\345\345~:\356\333\365r\332\267m\235\226u\331\266\375|\272<=\276\375\370\365\227\267\177\377\353\307\375\323\307O\331\253\261\253\245\346\320\353\232\333\343\345\333\357\337~\273\177\270\235\326\333\313\371t.q;\235\217+\327<M\353\266\357\307\343\361\345\323\217\377\370\376\371\351\327\307\365|\334\2753\312w\271\315%\253\301\304\370\366\343\317\317\237\277_\256\267\333:=_\326\375\030\333:\267y\221\213\237\004\270^_\036\277\377\361\\S\276\234\317k\265\326\030\327\265\251\225\354\265\n\351\374\367\377\374\365\365\355\371\3065\307\351\264\315\307\224f\276n\004X\313r<\236\316\257\363Zk\2529W;\216\243\266\326\271\316\247\\r\364F\353\351\371\374\372\361\345z\275=\335\357)\305\343\266\257\363\334\262\344\277\254\373v\332\256)Y\233\3628h5\216\303h\254\367\316w6\3449\325\350\3061\247\355r\273]>\\n\347\263\317>\255\253D(\265\266\266\256\324\337\234\3621\0305*m\346Z\366\205\032\274\357|))M\261\270\321\370\363v\272]\256\227\215\303\213w\333:\257$_\033u\320Nr\f\3319\247\207u\231\225M\353\322\2345\241\313\311\232\312\025\2656e\335./\267\023\331\226Vb\\\346u\233\032\001\326m\273*\345\252q\24193<\355\307\311\306\326\30282\005\357\\\\j\245\rvp\247\323\351z\341\374\2472\325\342B\235\246\245\265i\t6\270\336\224`\326x\016z\240\2711\347l\006\002\204\316j}<m\323\266\2478\230\371r9S\355\266\022r\362\332\245:\255-*:fM\324\346\357\377{\373B\367\227\331\247Z\3658\322\022\335\031m\246\345\366t\2716o\2142\361|</\265\3442M\245j\343<\335\035c=?\331I\333\277\277\177\340v?\371@~\306f;\364#\001t.\3556=\036{p\301\030\263\357\205\004\351\3754\027\346\024\322\3246\372\374\232\036\371\3655Z\347\223\347\344\032|`\230c\350\264V\246\224f\355~j5\246`|H\2450\274y\231C\320\336O\241A\027\377\343\217\377\345\303hcM\216(6\020\\\006\323)\257\3241\323\320v\234\353\266V\372S\tX\005\200\233\323\241l\347k>U\333\376s\032M\n\256\272\030Rt6\371`m\bd\340\364x\334\303\3410\026\206>M-\346\024K\256\251\021o\266`z\177\334\336\356\326\236\325h\346\350L.\211,\271\311\333\030c\356\254r\332\335\250\3460f_\'\322\3659\223\202\0241Mn\252\347\327\267\327\327\257\317$\352\335\f\032\rh4\201\216S\r\207u\203R\306<\335N\266\357\025\370\246i\021v\200\254R\251C\307v}\373\366\366\375\323\251\030\337\246\b\370\225v\316\222\211\3211s\\\247\264\002\t`\vTYg|\"\257\230J\232B\3661\372\330\226/\277\277\375\327\027\357\366\375>Qs\320Z[\320\347\034E\004\237\273\301\214F\257\273Y\\?\250@^!\270\030K\230\005\315\204\234S\234\257\217\353\2162\355\313\334\254Q\0262\305\034  \371\344\256\'u5\327\251&\376\360<\021\234\v1E\232\220\321\t\355\266h\323\224R\233\270%\025\v\270`\215\241H\222`\344\235\006M\343\232i\317\330k9\222\001\323\005`$\200Dr\246\225qE\310\237\224BB\024\263\244\212\362s\214y\351fm\225:\241]K\033\r\375%j\200) \241\026\370n\034\277\350:\337(\351\367;\001h\2406i\"\325\\\273\241\357\315\250S\323\220[Ec8\337\225w(\002\206\310\304KA\200\220O-B\002\365\2143}O\336\203\314,\227N\037z\37024\327\254\032\231\016\205\t\t\247\372N\031[L*^\217<\3073\374\327c\337\037\270h]?\360q\352\3740h;\f\v\002\251\031\022\303\f&\320\261i\231Y\026\261\0017\243-\223\367\326jy\362\237\377\\\350A\257\272\017M\353\3630\304q\360\016\322E\215\276\2326K\027\3624\307HO\351\031\245 \177]w u\216>\034h\346\330K\244\356\313\307\264\337\206^\217>4\3211\023\36480\002`\224\352\342b\0024\3223cx\\)Z\357#\220\355G\272\"\271t\257\327\371\374\351Hm*5&\237\265\347k\221s0M\001\376]Gy\\\221\262@#\346\"Z\006x\224\264c\354N\267\260\277\035\3350\000\240\234J#\003=\242\270)\204\352\337ik\2048#i\243Ss\211.* \'\225\310\245\273u9\316\237n\254\306\242ucz\336\347F\200\t\244\227@\302\321\312\006\223\261\225\005\211.M\311\337#\317\313$\206\241k\3554\177\374\364z\254\227\251\262\267\002\210k\352tZr\244\243<\034\344y\305\371\242\321%\006\320e\373\303 \r\355G\272\320M\323\275}\371\353-\3733hd\346\256 \330\363\207OW\000\031\377\311\300\312\201!\325\":\226\253S\222|w\240\023\374*\365>}\375\353kV\363\372\256y\001\021\320\323\343\363S\220\376y\331\177\326\364`\021\365\251\311z\220H\003\016\335\241cE\252\330wFT\366\257\257\237t-,\342\311;Zo\362/_\276\325\020x\026|y\326\302\240\223,a>1\022\200\342\031 \264h\266\023\364\253\360\362\366\262-\313\004l\310\322\350\374#\337\317\305\000@\300\312\363\207\301jH\204\334CG\002\"+T?V\223\367\256\220\207\262\341\365\204\207@\"b&\200\232\252\247\215\034\346$\000\250\265Q\231,P\315,\037\253\347RPa\355\224u\335\354\371\251\307G\314GXD\241i\325\372\243\207\"\357\v\300\272L\253\006\266\210\003\002!Z\n\207\230E\213\020e\304\255\023\263\241.(\217GbR\016\271m\260T\353a\240m\310\247O\303\240\244\260\224\250/ \b#\372;\312\030uT\030\fF3\016\331\216i5L\276\324u\277\264&\232\253k\f41z\264\222A\220=\002\202\036\222\200\021.v\207p4\252\357\210\313\214\026b\t{Xh\373\222\263S\305\340B\002\024\026\2615\304\201&\"\305\026T\213+\350\207\256\257-\333C\027 \3400\224\204\334\343\251`\321\262\004$G8\335\320\2078\271\236\305\201\266@O-\v\234\347\025P\354\273a\237\202\004\000\360\330\275\261\037l\201\305\204\340.\f\324\250\346\211\246\346\t\236\3118\304\316\35018\005Yu\031u\327\251+\256Kw\322\027>\034\374\240\300\321\304\277\314\234\210\235J\264`gNj@\355\213\243\243`\006`\215Jd\264\353,\226rg\n\000\216\274\006H\252Y\206S\362\2633-r\222i\354\226TV\255*6*\240\346#c\025D\365\a\335\003\243\375t{\262\035\000\003\233 \230T\341+\367\221p\016\3163HKW\247gv\2213\301\266du\320\360J\t\262\244\342\355x\272\263\332\320&\245\262K\366p\300\017\261BD\363e\005q\247fIj\271b\322@0#MZ\224\350\320\365v\336\326\333\275\305\016\025\347\004\211;\036f\331\236\332j\251_\240/{\200x\243\032\003\304P5(\330\355\255d\240|\251,\327e\241\004.\347M\204\335\036c\240D\303!q\360\340\300\344\373\307\244\330}X\203\240\004\231\340\3219\304\210N\370\020\327-\201\003t\322\317\333\344!\035\033@\205\202S\202\273,\372:\227\307\243(\200\340\021we3V\212\304R\355\a\276/\367\313\355\342;\027B\331\237\177y\333\3430\034\000m\000\333\016j\037q\0232\265\201~\t\366\234/\016\253)\253\327\207~\264e===\177xD\234\252\257\307\307\237\'\3618\350u\336\321c\343\360\327%\tc:\331@4\b\347\360\3442\273\323\b?`\242\366\363\307/?~\334;\fcZ_\376\273\262+\304\221*[\353\202\016\002\261\237\227\260\206\030\270\002\334\021_\200G\223\230\002\366s\373\376\307\237\277\211\321\364\355\371\273\353\031\226/bj\323\374!\305\303\373\303\357^\206\241\217\006\2760?\353\212\370\v\314\037U\341\356\277\355\027\\\232\366\365\305\t\0270?Z\344\276\355N\236GR\347\272n\307$\253Y\226\001P\323\r\v\002,\2128Kp\025\r\001\214+Jt\033<)/\001R\2265\030\352\276c\025q\357\373)\023\263?\340\364U\364\342L0\000Y\020\005\273:\006\344\211F\000=\312\224\315a\320\224=\232\270my\332\2666\037\357\217|\274\264(\376F\211=\301@c\360\210 \306\216\000A\354\225\321=6\026\v\034\336\267\277\366\370\373\232\347#\372\222\004Y\3503\257\227\303\0302 \003\'\274Ol\200\267\312\033K\362\357\365\243\261\002kY\372h\336\273\203\350\255\000\341\247\027Aa\034\n\217\274\002\234\234dKji$\001j\337Ch#/n\2622\031\273\260E\026\027?{\001\001\257;\210){R\314N@\372\n\257\023\204 l\355bR\207^\001\016G;\3317\034\357\320;Z\237\262\037d\203\276\257!\222\250\220\201C!N\nXapI\320\326\201*\314\216A\326q[!\312\306~\277\177\320\310\t\344\025\025$1x\235\331?J\2746D\203\256y.\204s\330\274Q\017\302OV\315v\333\225\314Q\206\322u\222\r\241C2!?>P7\246\206V\330\214\367`\214\336\204\331*\333\211d\2767\3540,\274\t*\375\263\213\377x)\311\205\031\211K`M\263\0224\320\267\031?\305\213\036\236T\333nTRx\210\200l?6\270&f\346=\310\317AH\376\300\\d\231W \322\207P\354s\\\234\227\265\341\376\037O\340\276\251\345{,\253\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_map_assets/expiration-distance-disk-tile-5.textpb b/core/res/geoid_map_assets/expiration-distance-disk-tile-5.textpb
new file mode 100644
index 0000000..387bbdf
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-disk-tile-5.textpb
@@ -0,0 +1,2 @@
+tile_key: "5"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000@\000\000\000@\b\000\000\000\000\217\002.\002\000\000\n\321IDATx^\035W\331n\034\327\025\354\351\345\356\373\332\353\364,$E\312\262eYN\340\004\b\022 H\200<\345)y\310\377\177H\352z$\200\304\260\373\334\263\324\251\252\333\r\303p\271\020:pa\274\325BP\252V\311\331\320_\372~\230(\031\206~\030\307\2110\356\202\025BI\251\230\225B2\374&\204\024\3350\210\313\3452NBh\217\317,\204O)Y\301\373\021\257\222\021/O#\002\311\024\360-^\221\332z\211s\264\261Bq\322!8R\350G%\215I5\347\325*_\222\346|\034h;}\232\2504\316\030c\255U\222s)\255w\0229R\'\270$}G\b\233\220\0023\306;\263\247T\274k\037\211T\351H(\245\023ERQ\031\'\244UJI\245\2037\222\021z0A\306\251\233\210\230\372\3762m\b\020pzL\336k\245\263\215\206M\212SJF\202s\225\261J \023\305\225O\316\021\374q\341B\022\004\020\222\242cC\310\321\aA\2716\305\332\020l\216\316K$L\31041:\216\375\200\323\245A\023|\b\224\2502\261\273\245S\337i\206\363Q\202m\265\033\212\b\331ZcB}\256\321%B\2317\255\363|\030\230t\355W\031\202\"$\032\356\320\335\313\245\313S\337\232(\275\224Q3J\031\367i\311!\326d]\n\204\350\2424\343\312![T\201\367\215\211\322G)\270\306{\227\241\323\343@.=\221\2221L\bEse\274\317\271.\347\313\227\212\202\244v&\205(\306Q\002\'R\271\020]\016x\234\221\276G\023\345\200aO\n\337p\f\211\v\005@\270RkJ\363\333Ok\314\316\343\273\270l\023\006\n\374h\035\262r\263\021\030\204\354\273\201v\300\3330L\321jF\bN\307\a/\204\\\312\272\034\333\222\225\322\202)\357+&:R\207\371X\340\360TZ+A.\335 \273\221+\264P0\3464\227\316\031\035$\362A\204\224\352\226}\233\274\213\245\324@\350\324\340jP\005\002\006\215\351t]\357;\f\211\231\3409_f\340\a\030\260\025C\262\321\307\230\213\327\022\370\325\353q_7dH8N\002$\262vR\0231\"@\355(\302\342M\316\317-\3703\006k\023\233\260)*\2259\205y\251\301\250\354k\3354E\217(a\322\370yc,\3232t\227\276t\350\277N\3006w\253s\325\270T\243\003\274\215\ni\306<\2637\016p\002\222\221\201\3268\316\355%z\306\3537\004\350I@\200q\342Q\243RlK\002\n\263\266\250\227\331\020\327T\003\n6\336x\355\"&\254\214\301>\334fi\343\017\347\251\373\256O\b0\364\224T\f\305\274\027\233\262\301\256\242\217\300.\323\347\271g\r\340\350\274\254\331\246H\031\023\202\a\357YY~x\276\242\005\223J\000\322\210\366z\246\3463\247\325\257{\260\0163\002N\364\272\037\017\311\265\326V\352\315\' wD\031W\245j\231\327\0053\030\274(\035M\321\003\"\000H\315\327\325\324Z\202\326\036\375\310\347-\227\031\033,\230\021z7\323\210\305\234\250\306d\347\307\327c\356\273\v\t\321wT\337%\266\336\316aG\325.\327b\271\325sT\363\313\236r\335@%&\273\307*\247)i\037\255;\364<\247\354Dw\031S\b\241\303\366e\n\214\316\031T\220\313\262Zp\225FS\000\337\353\016\fY4u\373t:\356\025\021:f\273\311\3031\n\f`/c\001\241p\251\251H\261\350\342\264{\0244\336\315\311`/\\.\332,\005\240\215\327\347uI\206b`N\345h%x\3402\201\000\347\243S#cm_\254\223\331\351\370\251l\321\206\224b\006\353Qn]\215\326\272uY\346e\251\025}\004\r\001j}+\000\200\366\241\033zD\305x\215\301\237r\315\t\345$P\t\bA\213h@\216&\256~\aKm\327\343~\r\306\201\033\030\210`T@\313\0000\r\323\202\247\202\307\256\203\254\021Er\254\202\v\350&\202\362\236\331|\004\303\365\276\275\274|\376\274\204\300$\260\323u#\003\357\203\221@&q\2432z\360zKZ\240\335`4\353\234\002\251s>I3\227\022l\375\351?\377\375\355\313\373\025\260\360\303\340\372\016\370\301\003\330\207Q\250*\315\001\226\002\243\000i\2145Z\000\232\220\037\aU\006\347\327\305hu>\377\371\333\307\016\262\213\001Y\311\3412\200p\255G&!ke\347\b\342 \2244\362\034\301\201\034\324\001!\313\331H\236\367\245||\256\373\277\376q\265\353\\\226\271G\365\364\202\001\222$:n\326\303\224u}(I\'\256\344d\025\301\331\200\226\02018.\364\022\342\2535\267\373\313/\337\357\321\317>\223\236\3228\\\2721\351y\355\244)\217\347\363\361\372\260\320\f\r\3162K\242d\302?\272Y=\030_9Hh1\366\361\375\333\367\017\264#*F\336~\261\340\343\236\356\327{g\322\355\375\353G\254\333\262\326\240\005\200n,\345\214pB\002z\032\b\2045`\321\324\355\373_\317O\261\240K\343\364\370+\277H\214r=_\273\262]\313\275\306P\227\nj\021\272\361\201\223\236P\264\001Z63Hc\331\316y\271\237\277\236[\314\2336\343\270\376\252\240&\335p?\216\356\374\355\036R\024\302\004\220\212\a\232\231\213N@L\024\265\244TNh9\224|\375\364ct\217k\235\363q{>P_\201\236u\303~\273u\354\347%@\361\204\242 B\020\263\3454y\201.\203%(\264\210\031\177\b\377i\261I\306\363\353\227O\037\307\037J\036\311G\316\320\263\375~\353\210\311\240\035\031L\333zA,\304\224\031\002\222\201\f\030\327T\300%?\257-\276\316_\377\3748_\337k\232\006\366\"\273\313\360\274_;Ld) @\r\032\220\006\v\v\345\204\016\202:\360\237\0064\035b\026V\006\321t\347\237\376v\034oo\337\236\274\357o\023(\365\231\317\016\310\335Jz\333\r\332\276;CY5\212M\200\223\2021\020^kc\300\357\205B4Y\2126\226\374\271^\177\240\203\206\250\311u\333:NG\a\031kk\240\017FD\333c,(P$\247\021z\310\245\307>\307\345\372\351d\251i\263\311\363\376\305\022\215)\232u\237;.F\352C!v\"\362\235\303n\350\276G\000\370\"K\306\2364\320*\025\303\266?\036\302\300\244\300+\335o\277|\301.\\\206\373\272\272fq\230\327\261%\24061\255B\f=\021D\203\t\246\2619<LCs\235\036\217D\024\245\316\302\312\325\363s\343dqn\233\353\bS\002-7\020\226|\354\233\267\313@\032A\301A\300#\"\000\003:a\225\324+,\025q\331V\b\330\233nk|;\257\313\334\021\237\257\213\211\320e\377~}\024\374\200\247T\026K\335\354\325\000\345\204\037\204\254I\243\246Q\201L\b\277\375\314\246\3762\272s\001\323u\023\315\v\3100K\236\316\375H\300\003\244\025v\004\026\223\264.\362\006/\346Tss2S\314\a\313\214}\035\371|\234\327}\2076\272#{\301\264\370x\277\302\300a\232\232\343m\006\217:\342\034\n\216\263\312\020\206\246R\005!\355G\344\006\253\260\316\327s_\266\316\305\274\276\336\035\277}\375;\002\271\310&\312\301\024\214\2148\3532\001?\214Y\240t\302\020aaF\006\252\000\021\313|\255q\313y\353t*\361\330\266e\377\337_\016\255\334\332\234k\363\307xyl\346\003\006GYX\003\252\034\322RS3V\200(,-\244tC\tT\224X\217E\313\267\177kx\027d>\301\341\2168\037\001xK@\231\320\354\2230=\261r\274\364hpS\235\230\363\262\264\000t;L\000\237\001\rJ%\336J\'\315f\343ui\031\\\2327\271\315Eil\031\347\227K\005\227\032\201\304S)\307\326\255\353\341a\001\274\237\b\350L\260\t\375\341\b\000\262\266\001\302\256|\330\022\024\336Y\016Di\336_\nq\024N2\305T\353\262v\373\331\354\271\017p\3050\324L4\267\b\310\264\372\261\231\315tBs`M\300\f\fh\a\363\216\274$E\361N\252\245\334\272\027@\023\"\202*\030of\000M\227\034\345\203\vy\320\316\266\345\t\306\307\002j\025\020b\314\200\315p\3331\304\245n\307\336\025\a\345H\360\217\b\000%\300\032\312\001\307s\017K\027\363\022-lo\366\241\031\263\306\266\022\312\301f\v\017\345\363z\\\257o\235\306M\210\206\200\214\220?t\221\300|C\362\261y\214\032\034q\334\326:\033W\022\207Tk\351\030\357\207\232\275\214qC\200\333\263kv\002F\023E0,\001o\327\003\\G\230\005\305JW\217\307c\376\275F\357\271\366\t>\026No\2341\200\274m\353\365\361,\235\001\361q\301\201U\2600\003\337\302\236\201\312Q\274\267\371\343\207\367+\304\020w\261\371jK\205\3278\024 \022\204\317\333\276\\\257\307\223w \016`\177\262\300\017\372\213\342\'A\235w\311\241\266u\373\214\273\213\365\326-\271\306\265\270F.Xr\3643\347\373\375v\234\216v\220k`\2259\034\215\344\261dp&\020{\330\237\365\276=vx\356\202\025\263\266\304\274k\320\016\240\312\337\245\\\326\217\327\347\313\025^\t\306]\033\335,$F\300\a\240\tXb\277g\275/0\230\355*\345\004s\270\202\354\226R\220\317\300\326\323\3477\004x\205\355\354\004n((\002\003F\v\241\354\231\214\226\363\022\340\223\2269\302\345z\017\303NS^p\363\304\245L4\323~+\371\307\337>\037\270\234<;\b\242\006\327\215m\005\031\247~\265\243\344\246@\356\"4\023\001\000\006\334cay*\236\244\0327\233f\203\365\217\267s\021b[:\270\0214\001\3149\265\335Wa\211\2362L&n>>\301u\0338\264x\\\037\316Sb\345\024\310\002I\320\363\r\367.\361\361\366\271\003\3341rJ`6q\321\004\355G\300,\315 <\037\236\031\240\026i\251\256\372t\336\223F\005p\337x\234Vp\234Y\326\237\337[\000l\232\342\006\227qBc\211\021\303\202E\203q.\245\235E\345z=p\213H+\\\231\024V\227E\2206\257p\256\267?|oc\204\362\340\256d|\301\315\t\350\270\303\272\342\276u\274\024\334\221\005b\324\375\226\332\025\022\202C<\020&\232\016\310y\275\375\361\227o\337\376\017\034\345\351>\bI\311Z\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_map_assets/expiration-distance-disk-tile-7.textpb b/core/res/geoid_map_assets/expiration-distance-disk-tile-7.textpb
new file mode 100644
index 0000000..371e9ec
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-disk-tile-7.textpb
@@ -0,0 +1,2 @@
+tile_key: "7"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000@\000\000\000@\b\000\000\000\000\217\002.\002\000\000\v\366IDATx^\035Wi\217\033Ir-\326\221WefeUf\335\367A\026o6\373RkZ\323\032i\244\031Y\353\321`\327\213Y\333\203\005\f\333\037\0260\f\370\323\002\376\356\037\356\250n\200\r\202dFF\274x\357E\224E\034\227\307\rG\036&\n3\021V\233\306D\230\272\016W\346rIl\333u\354\025N\353$\020\001\027\201\220B\006\234 J%\245\2142d!\a\251 \b\302\204\370\204\a\375fh\207\276T\276e\371Eys3i\027\002X\250\254\313<\tC-\204\362\205\020\234P\001\247\024c\330\362<\027c\314\202&\321\252)\212\276\236\306`\265ZY\253@\266\367\267\247\320u<\307r\213\266\310b\245u\030\006\234\371\214R!\002\202\ba\334\362\034\033a\354ka\302\270\216\005\347e\340\257V\266\307\022\271\276\273^2\265\004\260\315P\025\"4\252\f\245`\f\002@\322K\034\346[\266mY\360\317qm\333\017\363\276\331n\367\215\263\262]\027\a\373\375u\277m\345\n\257l3\347B\206J\206\220C\310|\312\003\035\004\234+E-\033\322\265^_\020Ep\345+%mk\005\230\266\233\355T\2175\266=w\345\326\261\222\034\362\206\3735\367\001H\315\031\225y@\254\005f\370{\r\000\021\204\037H\t)aB\342y=\024\225\353\n\f\371\311T\"\017q.1\026\320\f\025\302/#c$\265\260\a_\344\271\362\035\aJ\267\021\300\vE\301\255\2104\327\241\031\034\307\307\253\225\313\b\203\337I\036P\3423\310]2\306\303(\"\334\n\212:g\332\204\304u\\\3575\035\033Z\200\260CI=\2366\265\v\367\332+{\345\320\bc\"\341b\037\316\252\000\370\300\002\204\250\225\313$%\236\313(vl\202 \ndbCM.\203.\303\275\256\207\005\204\264lG\'J\022m\000\n*#m|\237\272\b\031+\226*\361=\327\v\211g\273\236\01788.\362\201=\216\347A\213\001c\034\"@\331vd\026\032\252CH\037\002\b_P\212<\a[\261\016\363Dk\024C$\307\021p\320\226\302\037 gD\025\\o\257<\271\202^\001\267\v\225\2624OC\310\vN\373\v\366(\263L\234\026\271NQj !\a/\314\265\223|Cm\333\343&\t\031]0\201^y\216\250\241(\225%I\nU\340\245wI\354\373\226\311\322\024\302\246\245\353r\274\\\004iUu!\035(!L\323X\331\356+\323\210\203\363*\004\372s\017\200Z\216\307I\000\\\264\242\254(\333)\003\032\253H\272\310\361\b\360\201$\312\261)aY\020T9\301P\002\206vJ\343c\212\\\017H\v\360x\032)\337O,S5U7\366\371\020\344\201D\020\230\247hA\022\030C\002\255u\222$\001\265\035P\275 \212\003\377\005\v\274\245N\300KI\251\255\270\254\233\"M\265\016\f\021K\000GRH_\204\256M\002\223\306)\004\b^\333*\025\205n\271DJ\001\tp%D\034\a\322\312\313nHu\026\252\"\3174\360\300q\215\v\327\273 Q\270L\307F+B\\\240\222\343b\272t\026\000\000\vp\b\362#\235\305\211\225u\323<wy\224\344\271\314)\244\340\202\033-8!\344\363\250L\301F\200\370\340\020\200N\300\200zQ\030FI\022\a\334W&\016#+k\327=\257\213\".s\311)\206\b\b\356X\364m\034\251\313\301H\002\225C\177\034\365z\261\200\262tb\f\030\v\242\222\t\253\354\352\232\247\355\330\224\215V\212\260\305_\210\r\025\240\330\017\223v*\300\351$\363\220\215\000\177\344\330<JU\004\257@P\304\245\344\326i\236\323\315\361\374\207\276\316\022\024A\'\201\320hQ\225[B\215\331d\350b\177\016\202\"VD{+[i\031)\342\005\332\347\234\tbi\223\232\350x\377\353\313\241\017\301+\"\327A.\206\343\266\303\220\211MN\2120\213\002@\177\265\250\035 \362\021 \f\355\320\320R\306l\v\a\202\233\254\352\373\355]\344IN\210/\201\345\256\a\036\347\270@$\223\304U\035\270\216/@Z\213\246\204\324\230 \206p\b\375\024`^\313\247+\307\t\262b\312\263\fd\206\004%ha\212\343W`\200\240\262\f\354\306%\311\240(H\035\371`\t\004\234\\*\025x\236\365\032\326^B\230\260\355\213\210\270.[l\323\003Y\262\004h\020\345q\026\n\220\202\254\347\347\306\261\203(\242\f\006\021\230,\006\223\262\300 \302\252\360V\256M\351\335P\b\344\272\0043?\037C\320\005P\022\306N\232@/\301\026\213\3762\256VI\000\212`\034#I\340{l\261fw=\236z\035\300\251\247MDaha\237\245E\271N`z\001\a\235@\230h\251AVQ\221\331\356\020\004\221\240\212\222E\372\016\2678\225w\317\337\017\025\363\221\237]#p\243\240\344\030\2479\363`6`\360&\214\\\026H\027\314\b\212v\344\371L\022 B\340/\302!\324Z\v\321\275\177\274\3139\'\352pl\221Gi\323EC\b(-\204\b]\0271\327\365!r\342/\236\331\036oN\323iN94\n\344\302\254\315)\b~\270\333]\204@\264\377\330&\036%\244\243\024\\`\333\355n\266\003\345u\203\020R6a\320\033\327\230Sw\254F\243@0\204\004>\265v\363V\326w\273\301\207\301\241\036\037\214\217PP\270nH\366\327\251\251\347d4\231\300@\201\305k\001\372\354~\206\241(\"\240*!*\344\304\032\242\310g\277\274\277\2251\f\372\341\232QN\375\224\223r\267=\314\365\310\351&3\245\037a\350\252\207\021\321i\252\200\200\257\214\242\221\346!\266\272$g\230\315-\371\256\265m\246\312\"\364I\232\232i\274V\227&dMi\342\006\320\023 \321@h\2356\031s\026]xD)\030\264\310\352B\363\214I\"\231\274\001\362\365=\005\032\362\244\234\372\2428\233\020b\353\333\271\216\242\b\364%\3454g\257\232\000s\240Z0%\205k\245\252\373p\242\006\023O2\333\215\272\327\241\257\362\247k\b\236\274O|\330}\250)\f,\032\266\320\022Y\313(\267\035\254\264y\335x\\K\211\361\355ou\2312p+\272B\351\f>\001\323o\374v\366Y<|z\027x\030j\027\322[d\000\307-\210\344\211\310\204\341\"g\216\255\324d\017\177\330\375\365\247\207\2200\300\026\027\330\367=\302\374\340\223\241\261\326\227K\311=h \270\032\334m\301\2145zq&f\264\f\300\326\251\025\267\335\365\224\375\372\361\274\216\250\267Z\325\224\320\034F;fo\277\277\275\257\304\216s\327\215Z\204\300\324l\317\366sE}/\004\2150X\024L\b\001\262\252}h\364\327\227\323\224\004d\265\002_\005\272x=\346\333\367OO\037\276l\375\f\372\235C\363\"\310\334\363L\304}\304L\no]\016C\324\aS\335\336\253$y|\334\200\323\332v\264`\341\322\230\370\347O\335\347o\337\376\344:\337\375\372\241\031M\0369\204x\036K\005\327a\226\226\315R\207\017\333L7\354\036\032\251e8d\006\bb\263h\v\275c\204l\177|8\234\177\177\304hx\336\327u\227\372\230A\366qV\024\221\n\362\242*`\332\201\301[\315\270\335?\223\241\016\353L\033\360\377\344\323Ow\030a\317\r\363\254\3041\246\017\260\234v\275pi\003n*\362\274.\263\\\253</X\35092\006[\357n2\366\242\003\216u\334\332\253\247\317_.\227\f\374\302#\323H\250K~\221\222r_\226y`r\332u-,X0\000\312\274\314[\030y\332*\207\365\273<\377\334\373\024\023\352\333\316\323\371!2\225\241\036\f\241\254\244\364\351\347\215$@\317.\244\252@c\267\036\272\256\033\273\315\241\355\206.\002Kk\372\323\307\351\335\373\036\306\317k\000\357\374$\373\302\003\006z\024+J\263\227\257;@\204xJ\251\315~\\\017u=\232\271\352\367\363\361<M\205k\325}\237_\376\3742Dc\004s\002\255\274\315\341\361\002\213\000\362\370\342m\214\252+\354C\304\213\342\346f\n`b%Qg\352\252\336\314\323N\244io\325\2722\367\377\374\262\237\207D\371)0\0012\255a\332{\005-\030\301\"?=\207b\366\300\031\313\227\3073\270T\035\365\335\270-\273y\322A\252\215\325\036\313\352\370\365\237^\316y\036\207<\002\262\213\3656\355\327&2\361\236\300\200\224IDi\342\373\317\317\206\217\022E\211\330v\335y{\272\036\016Q\320u\326\266\251\252\313\345\315\343\017M\225\3022\017\214\257\373\252\032\347u_\027o\267\233\247P\200_S\231\252\362\361\3100=2\221>\236\177\376\362\307\317\327\3635\n\306\321\352\232\246\233\017\303\372\373\244\252\264\n\224\215\2735\004\030\247\272\236\267\333\246}\363\3764&\346\232\212\354\315\004\217\bqHy\365\366\333\227\367\315\375\365r\177\332\214\226R\311\346\334\367\323\224\314c\236$\371\264\335\314\035D\205y?u\305\364\360\371\353}\2479\330/\321\023 \340jLw\325\370\017\247\346\376\341\315a?\367\226T\325\371i\250`S:\315\323\220\227\323|}\231\306\016\306\3550\365\365\372\341\360\365\347A)\2148\314\034h-b`0t\275\237n>\276\177\270\207\307\t\213%\331x\031+\243\217w\247\r4\371|\\\247f\267\353\212\262\200M\346\346p\363\365\307Q\353n\006\371\202\312m\312\017\260\"\005\273\346z\177s\205G\242\275\305\362z\272L\231P\363\345\274)\233qsX\aj\277]7Y<\304\351\256\031\276{7f\315x\231G\030\352\016\246|\247`=\310\273c\267\271\336\277{woA\213\306\313V\262b:\2346%<\262\265kU\314\353y\275n\212l\3276\347\207\267\333\264\336^\242<I\227\r,\352\006H\206\234/\333\215\276{{}\003k^U\237\267\360\254\323\355\200\344m\337\266*\0306\343\272\357\273<\337\256\247\335\371q\277\333\357Ng\370\304\'`v<\204G3\017?\037\367\233\247g\bPt\335\356\224\250\034\"w]\277n\253^\210v\275^\217\375\030\367\303p\234\367\217\207\375\343<\034.C\b\236\200s?\252\236\300\273\365\2737Oo~\370\341\027k<\356\217\263\t\373\256\005\241M}[\305\242lZ\310\240\322z\354\272\303a}>\337<\315\204\356\206\242\027\332C~~\374\320\003\347>\374\362\345\343\277\177\376h\355\016\347c]\224\033\310\244\a\016\f\225\f\272\266m\247>\325\3618\r\343M\\\336?\274\257\240\374|J\313\030\212\240\364\363wm#\304\257\177\374\333\337>}\263\266\207=\220`\a\246\3255\303t\032r\2215uS\217e\031V\303\270\356N&\233\357\036\207e\205-\356\302d6\034v\263\337^NB\374\364\347\377\375\257/\237 @_4\331~\000\362\255\207ah\205(\3726\a0\263\244\354\341Qz\275[_\316\267\267\267\260\242\347\2730\034G\001\0327\277}\272\273=\377\313\177\374\337_\376jm6\345\202\3434\364M\335\017]\315\343\242)\263\242j\222\f>\337\214\233\371p\271}\270\273\275+U\276?\353\307\207sH\224\234~\377\323\315\371\307\177\375\373\357\177\261|Q\f}\267\333\326y\\7}\247\2022\352\212\242,\001\227\022\366p>\3177\217\367\017\267w\367o\263\354\374>\311\376\361\366\201s\217\374\333\177~}\371\370\333\337\377\373\177\376\037\341QF\003\316-\374\237\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_map_assets/expiration-distance-disk-tile-9.textpb b/core/res/geoid_map_assets/expiration-distance-disk-tile-9.textpb
new file mode 100644
index 0000000..59f386c
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-disk-tile-9.textpb
@@ -0,0 +1,2 @@
+tile_key: "9"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000@\000\000\000@\b\000\000\000\000\217\002.\002\000\000\r[IDATx^5Wi\217#\327u-\262\226W\373\253}/\026\311b\261\212\373\316f\223\275\261\327\351\236\356\236\255g\353\2215[\317\264\244Y\002[\036\317\b\032[\262#X\260\244\004v\004\003\006\002Y\261\215\bH\020\177\n\034(\210\201$\310\017\310\347\374\201|\310\207\374\205\344r\202\024\032\315\356b\275S\367\235s\357\271\367\021v9\311\274\240\352\031Q\330(\205\236lxa\241\340\273\236\027\024\303\260\022\363\270>h\264F\375\301`\326J\306\263\357tO\317\256\246\032\305\336\373\344\371\326\356[70&\246u\216K\313AR\216\263F3\364\024\354\373^!\f\035\307\213\213Q\034W\332i\332joL\247k\263\225\255\315\235\017\276\367\303\337\235~\250i\301\a\377\360\335\323\353\327\353\234H\354\257!6\255\005QV)\226\322^\325\346#?\b\202\320s\355\250\024\224\253\365N\2559\233\314V\246;\307G\317~\370\352\344\352\223o~\367\331N\\}\347\263O\316&\206\b\000[\373SQk\325\242\254T\324\rl\271f1*D\205\330\327u\277X,\307q\226e\263\331\374\362\361\255\243\363\217\276\370\323\253\'\357\334\371\350\267\333\242\370\340\343\363-\214\305\332c\242\377\326\275fk\263\337n5\353Y\022\225\375b\245\222D\205B\301\266\254(.\306q\265^mLgW.\037\037\177\367\337\376\353\253\2337\357\274\375\342\225$\341G/\256o\031\262x\364gDxx\375\b\261{\275V\253]7\263\310\217\232q\\.\024\002X_J\313\345z-\255\246\323\325\243\203\345\235\367\377\365?\277\272q\343\376\323\323G<\277\372\344\301}\001\253\352\325\367\b\247R\277f\232\223Ng8JM3\254Em\340\263\\\364\\7\212\312\245R\265\326h4F\223\265\265\345\351\367\277\375\303/\256\\\3329\270\371|\272\367\370\301\307\027x\314\362\203\267\b\241\230\244\353\273\343\321t\270\232Zx\031WG\255,.\227=\327\017+\225\254\004,v\232\343\345\311\326\372\336\367\276\376\342\213m}y\266vOn\234}\370\325S\211a\351\372\207\004E!\2771\331.5\216g+aa\273\3409\353Y#\251\304\236\347\201\262\245RVk\267\226\016/\366]w\371G/^\354k\323\311\344\244r\371\326\213/\233\b\245\224\362\212\240\031:\317z-\303\033+\322\225\375\253\363\336\326\326\264\331\250\305\216\027\226\223z\271\334h$\315\346\305y\2672\330\334??\277u\375h2\306f\353\352\273\337Ld\272Ij\237\0214M\347\021\315\272\245zEn<|v\255\323\275\340\212i\263\354\004q\234f\265j\255Vi\265\326\346\235\366hc\343\370\370\354\372\321\312\216\026\215\266^~\275*\321}m\363SB\346)\222$9\006\313eM]I\333\206\276]gq;\016\213I\265\232ei\257Wk.\215\a\265\332\260\331\334?~\373\356\325\375}3\234\035?x\376P\022g\351\315\227\204 \n$E\321\034f\264\314\262\246\375\272\251\254otZI\241\224\266:Y\326\207\374h72\000\350\342f\351\364\354\335G\207G]<>\275s\366\035I\330\377\350\313\217\tI\342a=\r?$\203P#K\315b\271\325l\271n\263\035\247\203\341\270\327nw\a\235NT+\371\355\335\323\363\027\217\037^.\340\321\335\207\357?\341\330\316\355\317\177L\3602/2\034\017\313I\022\211I\232\262\272\323H\nZ\320\356\325\352\355\366\240\333m\265\246\355\222_i8\311\375\363\037\374\352\316\366\266e\325o\\>\277\205E\376\312\3133\202\323%\216bX\021\277\371P\v\301\"\217\323B\030\025\374f\243\327\353-\365\373\343\236\343\304\265\376\362\205w_}\375\347smo\307K/\236_\2724\226\233\247\257\317\b\236Eo.I\323x\236a\n>g\362\274\345`\323Q\232\315f\177<\031\216\333P\330\215\341\322\366\205W\277\370\303\247E\376\356js~\347\331n\372\'\361\203\237~\372K\002BG\274\310!N\221%I`\220\303\231\262J!N/hP`\375\321h8\364\313\335vky~x\373\365_\377\363\317.\237>\027\304\225my\347\306\347O_\377\376\037\377H\220\034M#V\346y\236e\020\'\3506S\346|\327\246\2644\031\366\006\323\351\372t\2206W\227\226\326V\217\357\274\367\344\357\177\365\345\227\317\020\332\334\022\006\233\177\371\373o~\375\233o\t\032!P\200AJ`\b\240\005\304#\250\222dRl\326\036M\273\335\265\255\371\2606\\]]\032\3166\217V\367\377\346\237\376\345\357$)=8\273W\362\036\377\350\247\257\376\3427\004\313\200\212\360rJ\r\\\215\241\251\034I\353\262\343\331F\022\330KK\223\303\033\a\313\275\336\336\336p\320\235\205t\353o\377\347\277\277\020\330`\351\336\275r\264uv\366\352\'\237\020\210a\030\212\345\005\201\2255\323\341h\212\314\363N\350DA\254\362\343\371\315\273o\235\256\257\357\356\316*q\255\035\222K\337\376\307\277\277\322\225\332\360\306\353\206\233\234\\\277\330|B0\262c\250\222\240\v\002\306*V\025\236\3163\256\356\224\223\2701\030M\227\017\236\237\335\276}R\213*\365\345F\273}\364\321\037?\337\344\315\321\225g?\351E\303\223\023O\275F\b\300[h\250*\306:fi\321\320\0312/D\305,\213[\235\221\353\036\234\277\376\371\'\247k\275\245\361r\265\334\374\301\213\237\235\v\254=\277t\377\323\241>\272\375t\264:%D\321\365\322\202\312aY\321$\212\242\030\206&\251b\322\254\325{\243v\253\266\371\350\303_\377\325\367o\034\017\207\255V\375\321\263w\236>\344\271z\260u\357\243\211\236%\005\256\232\022\026v<\307)\262\202*\b\232L/ (\0227\032\315l0\252\367\202\361\335w\177\374\333\3077\257\354l\256\016\252\357]?99\342\270yp\341\326\343-/I0I#B\2678\026\271\021\317+\252\253\360,\317\347I2\307W\333\355l4\033\266\312\315;\367\037\177\360\313\314\236ot\033\223\273\307{\'\207\310\335\336n]\275q\242w\032\001\217\004BW\031F\nC\216u\303\032\206\232@\240C\216I\332\235\341\322\244Q/\225\016o\335>\377\371dP\233u\272;\253\263\371\316\026\207\366\326g\343\v\227}\273\337o\016SBE4StM\216u\034K\026\260\242\212\024\324%\327\031\367FK\215\206m\367\347\363\313\357?\036)\223\331\265K\256R_\217X\2661\356\325\372\273\205\352\325a{V#\030\222\212\352\246\215\241\'\232XPE\222\024\251<IW\'\275\336\250\336\t\303di\351\360\316{\233\363\235\265\333\207\243\301\332\301!\317E\355\226\273\262\267\273\262\262\334\336\274\004~ 7B\313Q\203\330\267\025\335\'\363\244L\346\363$^\356\r\373q]\247\362T6>89YYY;\270\270\2661\337?\340\370\203V\327\335~\371r\377B\275\267w\221\020$<\016\203\240\033\227\035G\261\003\222rl\rx\244{\375^1\256\327(\222J{\263\365\275\331ly\272\273\2666\037-q\334\315\365\336\356\375\227\017\036\337\213\212k+\204`\325\227</L\n\005M\020\255|^\320\r\021\264\244\300\020\255\300\355\0264\272\336\356O66V\246\313\233\233\033\353\235u\206\331\336\330>8|\373\322\245{~X\353\020f\251Rr\242$q\034\215\345q>oBy\223 \005SOeA\217\352\252\030\307\315\316dyc\343\322\366\346\312\372n\233\221\306\303\313\233;+;\223ke\243V#T;)8\232\203\261\355\vH@4IR\271\034\v\205m\030\222\350\'\rU\215\343\244\326\233\254m\316W\267&\223\251\3070\243\341\346\366|l\364g\2111\034\001\200\241\204\032/\033\330u\301\027\240GH\371<\a\326 \b\266b\206a\030\024\313\225j\265=\234\255\365z\223I\237C\364\366x2\036\225\304~\346\330\315.a\031\272nK\034\247\342\202\312/\354]\244sH1\205<2\rE\324tE\325M/N\222r\251Q\253\266\352\"\313\242Aw\324\357\370\270\227\205N\334$*\266-I.B\206\353\253\252\203)\232g\362\274!\260y\222b\031N\341E\225\027\005\245\030\333a\251X\256z\nES\265\356R\307\257d\255\206g\307\036\221\264l[7Y\326\326uW\222\305E\177@,\317 H\006\212\226d\0268\311\347 9t\337\363\213%\v\f\024\325kI\235R\262V\315+4\034\242*i\206l\361\212\214ea\341\254\024\223gX$\36292\217\2400\350\305\362\305\305\b\226\345\030\262\352\245\264\254\2242J\252\267\352z\320\342\bC\322\261$\211\022\v\216\fmV\240\250\034B\264,\345\310\034\aa\344\340\202`\026 \274a8qV)\033v \227H\266\226&v\230I\204\256\351\222\"+2Z\004\235#\311<\221c)J\342\026+\t\202\200\350\363t~\201\000\030zP\210\334\202\256k\305\200\346\3234q\213\231O@O\223\024\f;\345\200\236\034<\a\000\214\0046\235_\000\344(\017\213\242\310\0024\000\346\215B\340)\262\252\206\241a\201<\340\245\210\020y\254\260,\t\357\024\\\323z\0233\022\305\034\221\177\003@9E}\001\200\026\\\300\277\276\242\b\252\351\232\nb ,\020\035\021\f\257 d\031~Q\221-\313F\213\307h\016\345\341\345\260\001$\341\310/\006*x6\005%J@\225\261\314\302\371\027\033\202~D3\fa\033\b\331e\337M{^P\362\235E\0044hG/\366/\210\"\326\213\206\241\a\272&\347\t\366\377y\201\337\377\a\300\212\"\341\250H\260\312^\034\204\361\240\227\306\034\354\226\221\241?\345)]\221d\350\231:h\204\r\325b\240D\026\b9\002</\267x\214\341\355\000\023&\315\270\336b:\207\361\274\332o\2339\350\3272\231\313\2034\264\310\261\234\252\2100\331\252\212\252Bp`6\020\034\363f\a\210\346\004M\023\bf\001\340\332\212\342\205\225l\330\347\340\005\202I\021y^\000\365\030\0309\f\003\026K\274\0007r\204\240a\235\002\206I\232\325t#\264E\000\340\030\023\000r9\277\022W6VX\370\026\033t\216b\270\005I4m(2\v\\\262\254 *y\320\006\362$O\220<B\262\356\205\256\253\023\252N\t\016F\224dY\305\356d\b\3743\220\201@\001\202\254\206\334v\f\314s\242 \t\260\223EF\345i\006v\304q\234\244\326=\'s\t\0171\310\320\220k\233\226\327\350\017\021I\344@s\000`\363\210\341e\310\177\210A\020$\340C\223\260\246\311\330\367\242\002\334\265J\315\310\315\nD\bC\205\t\246\bY\3444z\211F\202N \021\303\210y\206\023Dlj\006\213\241q\202\021\360\274\210E\216\263l\017\232a\026\265\266\002\325.\020\216\315p \227\016\230NRv\035(\300|\216\345i\312\242\031Q\226\025\254\251\334\342S\002\3258\216\027E\311\002\325\312A\351xR\vU3\"\300\rD\004\226bi\246\346E\216f\"\n\b\202\351\023\251\002\'\233\340\321\"/*0\177-\254\232\203H\004H/\331\264\315`\354\353\226U \260\241a\244j\246\251)\262a\352\272\203(lTL\021\261\034\317\361A\344p\234 I2\017^\bB\300p\t\224Bj\351~X\322u\025\313\004\206\r\312\n\374\255\302>4WW\0051(\245)b\221\ni\350\225\r\221\227\3404\006\"\202\313\300jN\340\004Y\226|`Q\024\025\216\203i\035r\004\313\252\002e*\033j\200E8)\264j\220\347\n\f-\020\221!\b\226\347\330JQ\326T\025\024\0211V4M\003r8(S\226\220)\222\226EPY\204\344\325\261\255H\\\220e\253\"\f\177\252j\351z\002/\212\003\253\0001\300\005Ly\005\005\212\003.Ma9Q%$\030\362\336\254\207\200D,\353\222\310\332~\273;V9\232\321\f]Q\324bQs\364EY\211`\006\222\022T -\025\211\246$\240\t\353\000@\211\210\227%pT\260&\360F\226\225p\251\232$.\213t\003\016\271`\327@\273,[XT\004^\342%\253\314B\222\222\024\202!_\367\t\004-\b\350\021\200r\360\177\036\000\200}\257\3408\256$X\274\345/\316\260\262]\006\211\0240^I\325\260\350c\226\001$\206\201\246\224\021\213u\034\224\032\214\230\022\270:\3148\220\373\256g\331\201\252\332\210\346\035M\325e\253\344\273\v\32689\322T\350\036<\'-\306\251(\2126\b(\024\216\005u`\342\205[0n\210\222\300q\006\f<V!\320(\312\021 N\327vAe\320\003\a\232\001;Ep\346\304\232\td\315\377\027US\b\376Q\205\227\315\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_map_assets/expiration-distance-disk-tile-b.textpb b/core/res/geoid_map_assets/expiration-distance-disk-tile-b.textpb
new file mode 100644
index 0000000..e65c433
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-disk-tile-b.textpb
@@ -0,0 +1,2 @@
+tile_key: "b"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000@\000\000\000@\b\000\000\000\000\217\002.\002\000\000\f\246IDATx^\035W[\217\034\307un\356tw]\272\272\252\253\253\252\357\327\271\317\354\314\354\354\314\354}\271\313%E\212\022\245\345M\261CF\026M\213\022\035;J\240\004\326C,\3312\002\b\016\024\304Q\002\333@\000?\344\321\017\t\220<$O\201\003\377\202\374\240<\344\214\v\v,\260\330:]u\316w+\353\343W\337y\377\331\aO\357^l\206M3\354\016c\335\241q\024G\nS\3548\210PJ=\337u\361\216\205#\023&a\230r\237\270J:\333\325\331\331\261\256\277\377\352\365\313\017\336~\360\344j4\355\017\273\032\333<I\213\266T<\240\036,\036\312@*\344:\0262q\231\006y\3059\241\310\266]B\220G\250u\375\345_\276z\364\255\323[\267\036\237\314\247\343E\200;q\335M\212\266\212t\030*\3440\250\021\033\352\302\367\250(e\320d\260\261\323\261\211\312\222$Ic\353\325\307?\371\344O\036\235\237^^^\234\036\356\356!D\213*K\212\274\310\263\210\375\341\032\224\371Q\354\271;\256\027\232\244\256)\305P@N\016\372\335\254lz\326\313\227\037~\365\321{o\335|\363\215\343\243\335\315~(\313\262*\262,\317\313X3\027{\310q=!d\350\334pYh\312\272\241\236A\366\364\250\031\317\206e\2577\264~\374\352\243\257?\371\344\331\203\367\337\275y\274\020Xe&\321:\323Y\246\265\024\001\\_\270.\017\003\321\331\261Ud\2469cR\372\aom\366\027\263i\333\033\355Z\277\374\217\257\276\376\352\363[E1{|\266\2448\315L\024\310L\253Xi\225\306\320\b\311\035\233R\206\350\r\326\016\017\030\363et~}y\260\331\237\017\207\363\365\306\372\337\377\374\371\317\177\363\301\3030\224\'\327GI\234T\025\v\362<\326J\'Y\032\307F\033\351\332\016\241.\276\341U&\225A\232\256\207\303\363\263\343\365\341r\357\360\370\330\372\375\377|\363\243\037}\364\302\230\372\344\2155T\357k\246\3132\366\0057\2216\211\201\341k\317q0\362o\220\222\207=c\232~;\274}qv<[\035\036\037\036Z\277\377\267?\377\344\263\357>\274{xx;\223\204\017\206\224\226\320\002\311\230\021\302\030\023\307q\030\n\2372\262\223\026EAy\262\314\373\367\357\337\271:\231NO\217V\a\326\027\237\177\362\027\177\375\342\342[\213\305\323\232\272\274\325\272\312t\b\337U\234\347A\270\355C\226D\222\241\235\235\270jK\252f&?\276\177\375\356\331\301\374\346\301jub}\374\342\263/\377\354\351\336j\271w\f\005p\035\364\212\272PZ\006\312\204Q\256T\232\304\275\312\243\324\336q\374(\017a:\311\336\315\353\3537\316N/.N\217\217\247\326\353\027?\370\341\367\357\355\255\327\207\353\034\341\2249mY$E\242T\022\372Q\331DY*\220\213P`\333\236\'\343D\t=X\335{v\275Z,\366OON\216f\326O\276\374\364\207_~\373\376\311\362p\025\243\246Ki\006W\315b\255\203 \316\353\321(A\030c\025(\025p\316u\244D\177y|\377\275\273w\356\\\236\235\236\034\034\034[\177\365\017\277\372\342\357\236]]\355o\326\221\335\317\240I2\2118\221\222\005I\"\201t\000\356 2>\254@EQ\334\233\256.\336z\370\340\361\345xx\262Y\002\016>\375\346\353_\377\355\207Ww\316\226{\330\256]WDId\002\306\003\251\225\364:\035GaL\t\361=*#\025De;>\270\371\350\217\036\234u\307\213\311\376l2\261\376\3767\377\370\253\177\372\336\367\336\276s$)\315\267\005B-}.\225\366C\017u\\\204\t\331\036\203P\356y\304\317\253\341\352\366\335\213[\253n\253\273\375~\277g\375\356\267\277\375\367\037\277\376\350\365\2433\201\363\2242\243\000\376~ \302\220\205\036\365\335,%\230`\004\324\307\036\264!\256&\363\325\371\371r}\030\351n\267\251+\353\277~\367\177\377\372d\361\374\365\273\207\324-3\354\233P\026\000\001\005\3437>\363|\311\260\2131\v\002!\240R`L9\201~/\227\323\262i\353\"N\255o~\361\337\377\362Vr\365\342\366\024\3434!\332\230\240\210\245.\253r>\034\017=\337\'\202\261Pk\317\023I\210\210H\263f2_\356\016\2065p%1\251\365\305\207?\373\347\'\253w^\034\016\b\312\025\253\373y\221\304i^tg\363i+\204\247\"\356P\035C\003|\277$(\310\223\004X\\\265\2756\213\322,M\255\017\177\360\371\337\374\354\326\305\323\303\222\3406\225\331`\\\032/2i\224\324%c\214J\031`\3004\363\b\347\036A8\212\343\254i\252\246\327M\245\214@\322\236\376\361\263\227\337\275{\361\370\264\360\360\356$-gsD\\\315@E\341\356\274\322Jy\314O\205\357\021\341Q\217:4\326qQ\225m\267\fu\004\262a]^^\335{xu\276Yw9\313\247\243\341l\202l\037\3336\362|\023A#\241\200\n\225\020A\bZb\244\203\203\300\310\274\004b\2524RZ[\213\361\351\311\333\253\325b>b^\bm\033\f<\2121\310\001\363U]\267U\325\024\202+\nl\30042\n\376\3145+\267\005\322\n*\030k\301vO\201\226\253\275\232`9\237\f\3522r]bL$d\244\363\242\314\244\317\004\005,\206\210\202<\b\307\023*\313\n\231V\371\226r\306\"\336\350h\275\332\270\330\347,\231\257\207\335\n\271\000C\245b\020D\035\v\317\205\376\203\305\300\017X\004\020\203\372\306d \330y\235\'q\034Xlrx~uk\317Eu\025\244\323\275\321\250v\034_\203\254\307)T!\016\200\333\367=\214\335-\236a\321\216\243\303\004Q\267(\213<\217\204\345\317\216\216\036=\216\202\242\024<\352O\206\243\310\rd\230\344i\242\r\243\330&q\022\202\227`\327\205Z\310\303\302vd\250\2024.\313<\317Sc\371\233\325\362\374\r#J\342$iY\002`\355\004\006d@\246\003\020\"RT\302\'P\000l\020#\360)\346\272\241\202\0236M\236gA\230Z\263\345\260\036\037\224\246\326^\225\2266\354\267\325\326S$ \b\272R\026!\a\fl\vx\214\201M\371\276\220A\230\345eS\306\240=\231\325N\274z\264\230\251@J^\360\216\323\351\270J)\237r]\005A\325tc\016\256\002\222\3401\356sJ\f\361\214\224:-\300\377\212\n\016a\345\211\337m\247k\2603\346\305}\a \350\202)\205\201N\353\241\347E\025\210\000\314\0202\002\343\202\3630\206yJ\035\205U]d\025\024(\254\243d5\351\215\346\275\270\210}\335/R\215\267n\300y\326\214\a\333\001b8:\024\360Y\240\022\316M\212\020P\302gmS\302)\312\242\264\312\375\305\270\327\353\366t\030g&MR\201\271\017\212\304\263\272`\004\222\t\310\310v\263\002:\307\200\342\032\n\000\320\251\204\t\024u\223fV\332\237\f&m^WJ7i\2524w\2740\304Xd\261\330\026\240>\245\234\v\005\247\221J\206\265p=\037CBA\016-\300\353\201\316\224t\347\223(\364\202:\315@\313\300\212\271\016\b\021Q \240\257&+)`TT)cQ\024\004>E~\004\234B\330Ei\331VIj\251Q\177>l\233\314\000(\244\340\304\366\211\307\t\220A\212L\206\206\373\b1\256\"\3511\nj\300\020\311\262P\021\217\270\016\257\2522)-\321\355\315g\275A\257\n#\207E1\340\330g\200}\352\a\251\342>\364\017#\225l\351,\340\340\230\021\256{\231\240\222\0030\241\215um\341\244\233B\346\002\301\361\034\b\037.\374k\000\362A}\002\212\312\231\307\224\311\245T*\227@\361\314uT\006\361K%\2328;Y^\024\201\345+\300\216\275\263s\003\f\034\003N\001h.\2061\370\220\217<\026\000\214@@dd\222-4\267\005\306\375\214\023\245m\326\301\271\b\032\213\271;P@0\310\214@:\210r\022\344$\364!S\000jA\204\240\300\326\024U\226\202[F\210\325u\323\3476\204.\233\330\221\027\344\026\206\000\323q9s\311\016\270\250M\005B\232\'Q\200\221K\030e\324\v\005\360Y\372\n\320 b\024\017F\303\222\332l\340\332\340\272(\212\255m\022\304\254U\004\232\r\001\320w\0211R\233(\f\267\262\352\362\255\222\200\034\000\023<\037\001\216\367G\203\032\344r\223:\016\301T\265\026\306\016\341qQp\314B\210\240@z?\224*\f\033\02586\000\t>\203\200\214\\\303n\004P\\\357\257./\217\306\304\023\016p\254(,\300\216\'FE!\034\307#\247vG\021\2601P\203\250\351\"\2332\016_\247\340\225\304v)\213\253\272\267\\,\366\232^\004\373\035\020\3562\262\240Qy\3357\322\a\'G=\262\323\261=\0004\310M\275[\340\255\fz\002N1\033x\216\315\222\301\254\005\354\224\2521\f,\0330\252j\v\220\337^w\v-;;\304\t\332\316\215\035\204\303 Lz\343\223\253\241\221\245\226E\244\322\246j\225\r\023\3304e\222j-<\221\303\261hP\314,\226d\317{\303$3\004\206\351\031\300\207\323\201\301\311\274\232\235\034\354O\002_Va\200L;\3547I\331\353\017\247u\336\351\220\320\317z\322\204\371d\317J\346\267!K\a\261\362\341\360\244\002\241\332\366\016C\236\031\235L&\3730C\024\v\001Tm\362\272l\233\246-\323\216\335AHM\353\254j\366\346\326d\005-\026\324e\002Y;N\324n\272\333h\337A\240D\252\350\357GL\326!\260\027^\a\345\260\256\363,6\021\274T\306\373\273\323\0062=\204\355\nb\003(\260\353z\330\202\373;~]\214m\000\247\213\231\257\343\351f\005\342\035!\264_0oP7\031\350\241c\343\342\374\341\361xR\346iVY\r\034 \2326\005T\340\235\033\026\220\302\207\337\035\016\023`2\206\320:\352\326uU\245=\243\023 \217\321\312\261\365\360\374]@\342\266@fQ\327O\027\213YQ\"\004/\213\033\360\212\202\265\343\205`E G\002\2744\020m\253\266\316\fQ\245\256\323\304\201\334\264nl\333i\267\252j\271n\260\\\036\334;l\023\307\021\322w\211\v\324tU\257\206\247\032\240\035t\325\363\303\030\b\r\357\f\327\005\220\a\262\030S\222\330\266\335\213UUY\230N\216\317n\336|1j\024\t\3008@\274]\207\362\252\255\343\214\001\321t\000\352\245\3456\"xn\247CM\024\247\203\201\313M\2344\020\350\n\v\313\365\275\247\227w\327\273-${\001)\"\020ntro\261;\311\263\256t\021p\306v<\276\365f\006\202\351`\316{\314\261\355\274\035O\024\v\245E\207\307\267f\263\305b3\356\305\261&\310\305f4[\354\206\273\253\275\335\274{\362\246A^\207\330dk\r<\t8r:6\356\330\324\357\217g\213ZPf\245\343\313\223\371|q\262\\\036\227\2311\240\006q\336\037\227$.\027\367\317\357\235\254\020C\341\032\366\2410\212K\023\372x\a\026\a\227D\302OkE\254j\363\350\370p}x\377p\267\017\266\020\301kI\207\331\240\354U\020M\252M\372\agv1\310L\030\325\373F\244\001E\2003\240\222\335\001=\300\304\332}\362\336\267\337y\347\346f5h\341\221\224\024E\336\326:\025\243\204\261\264\032\000\177\027\021u\201\313H\226\325\336\305\301\365\036 \312\335\032\216v8\001)\262\356>\177\376\374\263\a\347\233\301 R\220\034\213,-\253n#\260\201h0\335_\236\336\\\267\333\356\273>I\232\325\315w>\373\352l.\266\262\317|\354n\245\336\272~\365\362O\177\372\346\301\254\237\201\n\304\020\001\301\261\262\25071\030\321l}\177\021^\0344\020\227\021#A6\334\177\362\301\373o.\227\0201\003\220[W\362PX\337\371\364\247\277x\276Y\316\341\245\253!\274\345y\267\216\223\371x\272\267\036g~\020q\244\300\256\274z\274\337\204bw\266\351v\207\220\004w\223\b\002\f\260^\350\377\a/i\247w\337\243[\370\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_map_assets/expiration-distance-params.textpb b/core/res/geoid_map_assets/expiration-distance-params.textpb
new file mode 100644
index 0000000..b3bbddb
--- /dev/null
+++ b/core/res/geoid_map_assets/expiration-distance-params.textpb
@@ -0,0 +1,6 @@
+map_s2_level: 6
+cache_tile_s2_level: 2
+disk_tile_s2_level: 0
+model_a_meters: 322622.0
+model_b_meters: 2828.0
+model_rmse_meters: 0.707
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_map_assets/geoid-height-disk-tile-1.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/tile-1.textpb
rename to core/res/geoid_map_assets/geoid-height-disk-tile-1.textpb
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_map_assets/geoid-height-disk-tile-3.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/tile-3.textpb
rename to core/res/geoid_map_assets/geoid-height-disk-tile-3.textpb
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_map_assets/geoid-height-disk-tile-5.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/tile-5.textpb
rename to core/res/geoid_map_assets/geoid-height-disk-tile-5.textpb
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_map_assets/geoid-height-disk-tile-7.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/tile-7.textpb
rename to core/res/geoid_map_assets/geoid-height-disk-tile-7.textpb
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_map_assets/geoid-height-disk-tile-9.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/tile-9.textpb
rename to core/res/geoid_map_assets/geoid-height-disk-tile-9.textpb
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_map_assets/geoid-height-disk-tile-b.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/tile-b.textpb
rename to core/res/geoid_map_assets/geoid-height-disk-tile-b.textpb
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_map_assets/geoid-height-params.textpb
similarity index 100%
rename from core/res/geoid_height_map_assets/map-params.textpb
rename to core/res/geoid_map_assets/geoid-height-params.textpb
diff --git a/core/res/res/drawable/ic_notification_summary_auto.xml b/core/res/res/drawable/ic_notification_summary_auto.xml
new file mode 100644
index 0000000..908bba8
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_summary_auto.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M260,760Q236,760 218,742Q200,724 200,700L200,140Q200,116 218,98Q236,80 260,80L820,80Q844,80 862,98Q880,116 880,140L880,700Q880,724 862,742Q844,760 820,760L260,760ZM260,700L820,700Q820,700 820,700Q820,700 820,700L820,140Q820,140 820,140Q820,140 820,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700ZM140,880Q116,880 98,862Q80,844 80,820L80,200L140,200L140,820Q140,820 140,820Q140,820 140,820L760,820L760,880L140,880ZM260,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700L260,700Q260,700 260,700Q260,700 260,700L260,140Q260,140 260,140Q260,140 260,140Z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_satellite_alt_24px.xml b/core/res/res/drawable/ic_satellite_alt_24px.xml
new file mode 100644
index 0000000..f9ca7dc
--- /dev/null
+++ b/core/res/res/drawable/ic_satellite_alt_24px.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?android:attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M560,928L560,848Q677,848 758.5,766.5Q840,685 840,568L920,568Q920,643 891.5,708.5Q863,774 814.5,822.5Q766,871 700.5,899.5Q635,928 560,928ZM560,768L560,688Q610,688 645,653Q680,618 680,568L760,568Q760,651 701.5,709.5Q643,768 560,768ZM222,903Q207,903 192,897Q177,891 165,880L23,738Q12,726 6,711Q0,696 0,681Q0,665 6,650.5Q12,636 23,625L150,498Q173,475 207,474.5Q241,474 264,497L314,547L342,519L292,469Q269,446 269,413Q269,380 292,357L349,300Q372,277 405.5,277Q439,277 462,300L512,350L540,322L490,272Q467,249 467,215.5Q467,182 490,159L617,32Q629,20 644,14Q659,8 674,8Q689,8 703.5,14Q718,20 730,32L872,174Q884,185 889.5,199.5Q895,214 895,230Q895,245 889.5,260Q884,275 872,287L745,414Q722,437 688.5,437Q655,437 632,414L582,364L554,392L604,442Q627,465 626.5,498.5Q626,532 603,555L547,611Q524,634 490.5,634Q457,634 434,611L384,561L356,589L406,639Q429,662 428.5,696Q428,730 405,753L278,880Q267,891 252.5,897Q238,903 222,903ZM222,824Q222,824 222,824Q222,824 222,824L264,782L122,640L80,682Q80,682 80,682Q80,682 80,682L222,824ZM307,739L349,697Q349,697 349,697Q349,697 349,697L207,555Q207,555 207,555Q207,555 207,555L165,597L307,739ZM491,555Q491,555 491,555Q491,555 491,555L547,499Q547,499 547,499Q547,499 547,499L405,357Q405,357 405,357Q405,357 405,357L349,413Q349,413 349,413Q349,413 349,413L491,555ZM689,357Q689,357 689,357Q689,357 689,357L731,315L589,173L547,215Q547,215 547,215Q547,215 547,215L689,357ZM774,272L816,230Q816,230 816,230Q816,230 816,230L674,88Q674,88 674,88Q674,88 674,88L632,130L774,272ZM448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456Z"/>
+</vector>
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index ddedca2..50ff7c9 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -79,16 +79,16 @@
             android:layout_height="1dp"
             style="@style/AutofillHalfSheetDivider" />
 
-        <com.android.internal.widget.ButtonBarLayout
+      <com.android.server.autofill.ui.BottomSheetButtonBarLayout
+          android:id="@+id/autofill_save_button_bar"
             android:layout_width="match_parent"
-            android:layout_height="48dp"
+            android:layout_height="wrap_content"
             android:layout_gravity="end"
             android:clipToPadding="false"
             android:layout_marginTop="8dp"
             android:layout_marginBottom="8dp"
             android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
             android:orientation="horizontal"
-            android:gravity="center_vertical"
             android:layout_marginStart="@dimen/autofill_save_outer_margin"
             android:layout_marginEnd="@dimen/autofill_save_outer_margin"
             >
@@ -100,12 +100,15 @@
                 android:paddingEnd="12dp"
                 android:paddingTop="0dp"
                 android:paddingBottom="0dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp"
                 android:minWidth="0dp"
                 style="?android:attr/borderlessButtonStyle"
                 android:text="@string/autofill_save_no">
             </Button>
 
             <Space
+                android:id="@+id/autofill_button_bar_spacer"
                 android:layout_width="0dp"
                 android:layout_height="0dp"
                 android:layout_weight="1"
@@ -116,12 +119,14 @@
                 android:id="@+id/autofill_save_yes"
                 android:layout_width="wrap_content"
                 android:layout_height="40dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp"
                 android:minWidth="0dp"
                 style="@style/AutofillHalfSheetTonalButton"
                 android:text="@string/autofill_save_yes">
             </Button>
 
-        </com.android.internal.widget.ButtonBarLayout>
+        </com.android.server.autofill.ui.BottomSheetButtonBarLayout>
 
     </com.android.server.autofill.ui.BottomSheetLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2eb28eb..e7b1d09 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4375,9 +4375,14 @@
     <!-- Specify one or more <code>polling-loop-filter</code> elements inside a
          <code>host-apdu-service</code> to indicate polling loop frames that
          your service can handle. -->
+    <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
     <declare-styleable name="PollingLoopFilter">
         <!-- The polling loop frame. This attribute is mandatory. -->
         <attr name="name" />
+        <!-- Whether or not the system should automatically start a transaction when this polling
+         loop filter matches. If not set, default value is false. -->
+        <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+        <attr name="autoTransact" format="boolean"/>
     </declare-styleable>
 
     <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
@@ -5783,7 +5788,17 @@
             <enum name="none" value="0" />
             <!-- Justification by stretching word spacing. -->
             <enum name="inter_word" value = "1" />
+            <!-- Justification by stretching letter spacing. -->
+            <!-- @FlaggedApi("com.android.text.flags.inter_character_justification") -->
+            <enum name="inter_character" value = "2" />
         </attr>
+        <!-- Whether to use width of bounding box as a source of automatic line breaking and
+          drawing.
+          If this value is false, the TextView determines the View width, drawing offset and
+          automatic line breaking based on total advances as text widths. By setting true,
+          use glyph bound's as a source of text width.  -->
+        <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+        <attr name="useBoundsForWidth" format="boolean" />
     </declare-styleable>
     <declare-styleable name="TextViewAppearance">
         <!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6884fc0..d910940 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1273,11 +1273,17 @@
          null to indicate no split types are offered. -->
     <attr name="splitTypes" format="string" />
 
-    <!-- Flag to specify if this app wants to run the dex within its APK but not extracted or
-         locally compiled variants. This keeps the dex code protected by the APK signature. Such
-         apps will always run in JIT mode (same when they are first installed), and the system will
-         never generate ahead-of-time compiled code for them. Depending on the app's workload,
-         there may be some run time performance change, noteably the cold start time. -->
+    <!-- Flag to specify if this app (or process) wants to run the dex within its APK but not
+         extracted or locally compiled variants. This keeps the dex code protected by the APK
+         signature. Such apps (or processes) will always run in JIT mode (same when they are first
+         installed). If enabled at the app level, the system will never generate ahead-of-time
+         compiled code for the app. Depending on the app's workload, there may be some run time
+         performance change, noteably the cold start time.
+
+         <p>This attribute can be applied to either
+         {@link android.R.styleable#AndroidManifestProcess process} or
+         {@link android.R.styleable#AndroidManifestApplication application} tags. If enabled at the
+         app level, any process level attribute is effectively ignored.  -->
     <attr name="useEmbeddedDex" format="boolean" />
 
     <!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
@@ -1602,6 +1608,10 @@
          This is a private attribute, used without android: namespace. -->
     <attr name="updatableSystem" format="boolean" />
 
+    <!-- Allows each installer in the system image to designate another app in the system image to
+        update the installer. -->
+    <attr name="emergencyInstaller" format="string" />
+
     <!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags
          together. -->
     <attr name="foregroundServiceType">
@@ -2793,6 +2803,7 @@
         <attr name="gwpAsanMode" />
         <attr name="memtagMode" />
         <attr name="nativeHeapZeroInitialized" />
+        <attr name="useEmbeddedDex" />
     </declare-styleable>
 
     <!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 238f242..d4e727e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2998,12 +2998,13 @@
          will be locked. -->
     <bool name="config_multiuserDelayUserDataLocking">false</bool>
 
-    <!-- Whether the device allows users to start in background visible on displays.
+    <!-- Whether the device allows full users to start in background visible on displays.
+         Note: this flag does NOT control the Communal Profile, which is not a full user.
          Should be false for all devices in production. Can be enabled only for development use
          in automotive vehicles with passenger displays. -->
     <bool name="config_multiuserVisibleBackgroundUsers">false</bool>
 
-    <!-- Whether the device allows users to start in background visible on the default display.
+    <!-- Whether the device allows full users to start in background visible on the default display.
          Should be false for all devices in production. Can be enabled only for development use
          in passenger-only automotive build (i.e., when Android runs in a separate system in the
          back seat to manage the passenger displays).
@@ -4054,16 +4055,22 @@
     <bool name="config_maskMainBuiltInDisplayCutout">false</bool>
 
     <!-- This string array provide override side of each rotation of the given insets.
-         Array of "[rotation],[side]".
-         Undefined rotation will apply the default behavior.
+         Array of [side] for rotation 0, 90, 180 and 270 in order.
+         The options of [side] are:
+            - Option 0 - Left.
+            - Option 1 - Top.
+            - Option 2 - Right.
+            - Option 3 - Bottom.
          When there are cutouts on multiple edges of the display, the override won't take any
          effect. -->
-    <string-array name="config_mainBuiltInDisplayCutoutSideOverride" translatable="false">
-        <!-- Example:
-        <item>90,top</item>
-        <item>270,bottom</item>
+    <integer-array name="config_mainBuiltInDisplayCutoutSideOverride">
+        <!-- Example of at top for rotation 0 and 90, and at bottom for rotation 180 and 270:
+        <item>1</item>
+        <item>1</item>
+        <item>3</item>
+        <item>3</item>
         -->
-    </string-array>
+    </integer-array>
 
     <!-- Ultrasound support for Mic/speaker path -->
     <!-- Whether the default microphone audio source supports near-ultrasound frequencies
@@ -5378,19 +5385,20 @@
      and a second time clipped to the fill level to indicate charge -->
     <bool name="config_batterymeterDualTone">false</bool>
 
-    <!-- The default refresh rate for a given device. This value is used to set the
-         global refresh rate vote, and when set to zero it has no effect on the vote.
-         If this value is non-zero but the hardware composer on the device supports
-         display modes with higher refresh rates, the framework may use those higher
-         refresh rate modes if an app chooses one by setting preferredDisplayModeId
-         or calling setFrameRate().-->
-    <integer name="config_defaultRefreshRate">0</integer>
+    <!-- The default refresh rate for a given device. Change this value to set a higher default
+         refresh rate. If the hardware composer on the device supports display modes with a higher
+         refresh rate than the default value specified here, the framework may use those higher
+         refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+         setFrameRate().
+         If a non-zero value is set for config_defaultPeakRefreshRate, then
+         config_defaultRefreshRate may be set to 0, in which case the value set for
+         config_defaultPeakRefreshRate will act as the default frame rate. -->
+    <integer name="config_defaultRefreshRate">60</integer>
 
-    <!-- The default peak refresh rate for a given device. This value is used to set the
-         global peak refresh rate vote, and when set to zero it has no effect on the vote.
-         Change this value to non-zero if you want to prevent the framework from using higher
-         refresh rates, even if display modes with higher refresh rates are available from
-         hardware composer. -->
+    <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
+         the framework from using higher refresh rates, even if display modes with higher refresh
+         rates are available from hardware composer. Only has an effect if the value is
+         non-zero. -->
     <integer name="config_defaultPeakRefreshRate">0</integer>
 
     <!-- External display peak refresh rate for the given device. Change this value if you want to
@@ -6943,4 +6951,7 @@
 
     <!-- Name of the starting activity for DisplayCompat host. specific to automotive.-->
     <string name="config_defaultDisplayCompatHostActivity" translatable="false"></string>
+
+    <!-- Whether to use file hashes cache in watchlist-->
+    <bool name="config_watchlistUseFileHashesCache">false</bool>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 96c4bf4..0acccee 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -887,6 +887,8 @@
     <dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
     <dimen name="autofill_save_button_bar_padding">16dp</dimen>
     <dimen name="autofill_dialog_corner_radius">24dp</dimen>
+    <dimen name="autofill_button_bar_spacer_width">12dp</dimen>
+    <dimen name="autofill_button_bar_spacer_height">4dp</dimen>
 
     <!-- How much extra space should be left around the autofill dialog -->
     <dimen name="autofill_dialog_offset">72dp</dimen>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 830e99c..81a8908 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -143,6 +143,10 @@
     <public name="fragmentAdvancedPattern"/>
     <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
     <public name="fragmentSuffix"/>
+    <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
+    <public name="useBoundsForWidth"/>
+    <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+    <public name="autoTransact"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 558bae7..be96cc2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -396,6 +396,27 @@
     <!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
     <string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
 
+    <!-- Title of the cellular network security safety center source's status. -->
+    <string name="scCellularNetworkSecurityTitle">Cellular network security</string>
+    <!-- Summary of the cellular network security safety center source's status. -->
+    <string name="scCellularNetworkSecuritySummary">Review settings</string>
+    <!-- Title of the safety center issue and notification when the phone's identifier is shared over the network. -->
+    <string name="scIdentifierDisclosureIssueTitle">Device identifier accessed</string>
+    <!-- Summary of the safety center issue and notification when the phone's identifier is shared over the network. -->
+    <string name="scIdentifierDisclosureIssueSummary">A network on the <xliff:g id="disclosure_network">%4$s</xliff:g> connection recorded your device\'s unique identifier (IMSI) <xliff:g id="disclosure_count">%1$d</xliff:g> times in the period between <xliff:g id="disclosure_window_start_time">%2$tr</xliff:g> and <xliff:g id="disclosure_window_end_time">%3$tr</xliff:g>.</string>
+    <!-- Title of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+    <string name="scNullCipherIssueEncryptedTitle">Encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+    <!-- Summary of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+    <string name="scNullCipherIssueEncryptedSummary">You\'re now connected to a more secure cellular network.</string>
+    <!-- Title of the safety center issue and notification when a connected network is not using encryption. -->
+    <string name="scNullCipherIssueNonEncryptedTitle">Non-encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+    <!-- Summary of the safety center issue and notification when a connected network is not using encryption.  -->
+    <string name="scNullCipherIssueNonEncryptedSummary">You\'re connected to a non-encrypted cellular network. Your calls, messages, and data are vulnerable to interception.</string>
+    <!-- Label for the button that links to the cellular network security settings. -->
+    <string name="scNullCipherIssueActionSettings">Cellular security settings</string>
+    <!-- Label for the button that link to education resourcess about cellular network security settings. -->
+    <string name="scNullCipherIssueActionLearnMore">Learn more</string>
+
     <!-- android.net.http Error strings --> <skip />
     <!-- Displayed when a feature code (non-phone number) is dialed and completes successfully. -->
     <string name="fcComplete">Feature code complete.</string>
@@ -1572,6 +1593,11 @@
     <string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -2222,6 +2248,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
     <string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_writeVerificationStateE2eeContactKeys">update the verification states of E2EE contact keys owned by other apps</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+    <string name="permdesc_writeVerificationStateE2eeContactKeys">Allows the app to update the verification states of E2EE contact keys owned by other apps</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
@@ -6386,4 +6417,14 @@
     <string name="redacted_notification_message"></string>
     <!-- Notification action title used instead of a notification's normal title sensitive [CHAR_LIMIT=NOTIF_BODY] -->
     <string name="redacted_notification_action_title"></string>
+
+    <!-- Satellite related messages -->
+    <!-- Notification title when satellite service is connected. -->
+    <string name="satellite_notification_title">Auto connected to satellite</string>
+    <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+    <string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+    <!-- Invoke "What to expect" dialog of messaging application -->
+    <string name="satellite_notification_open_message">Open Messages</string>
+    <!-- Invoke Satellite setting activity of Settings -->
+    <string name="satellite_notification_how_it_works">How it works</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8b74cbf..58427b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -954,6 +954,16 @@
   <java-symbol type="string" name="roamingText8" />
   <java-symbol type="string" name="roamingText9" />
   <java-symbol type="string" name="roamingTextSearching" />
+  <java-symbol type="string" name="scCellularNetworkSecuritySummary" />
+  <java-symbol type="string" name="scCellularNetworkSecurityTitle" />
+  <java-symbol type="string" name="scIdentifierDisclosureIssueSummary" />
+  <java-symbol type="string" name="scIdentifierDisclosureIssueTitle" />
+  <java-symbol type="string" name="scNullCipherIssueActionLearnMore" />
+  <java-symbol type="string" name="scNullCipherIssueActionSettings" />
+  <java-symbol type="string" name="scNullCipherIssueEncryptedSummary" />
+  <java-symbol type="string" name="scNullCipherIssueEncryptedTitle" />
+  <java-symbol type="string" name="scNullCipherIssueNonEncryptedSummary" />
+  <java-symbol type="string" name="scNullCipherIssueNonEncryptedTitle" />
   <java-symbol type="string" name="selected" />
   <java-symbol type="string" name="sendText" />
   <java-symbol type="string" name="sending" />
@@ -3136,6 +3146,7 @@
   <java-symbol type="drawable" name="ic_collapse_notification" />
   <java-symbol type="drawable" name="ic_expand_bundle" />
   <java-symbol type="drawable" name="ic_collapse_bundle" />
+  <java-symbol type="drawable" name="ic_notification_summary_auto" />
   <java-symbol type="dimen" name="notification_header_shrink_min_width" />
   <java-symbol type="dimen" name="notification_header_shrink_hide_width" />
   <java-symbol type="dimen" name="notification_content_margin_start" />
@@ -3717,6 +3728,7 @@
   <java-symbol type="id" name="autofill_save_no" />
   <java-symbol type="id" name="autofill_save_title" />
   <java-symbol type="id" name="autofill_save_yes" />
+  <java-symbol type="id" name="autofill_button_bar_spacer" />
   <java-symbol type="id" name="autofill_service_icon" />
   <java-symbol type="id" name="autofill_dialog_picker"/>
   <java-symbol type="id" name="autofill_dialog_header"/>
@@ -3763,6 +3775,8 @@
   <java-symbol type="dimen" name="autofill_dialog_max_width" />
   <java-symbol type="dimen" name="autofill_dialog_offset"/>
   <java-symbol type="dimen" name="autofill_save_outer_margin"/>
+  <java-symbol type="dimen" name="autofill_button_bar_spacer_width"/>
+  <java-symbol type="dimen" name="autofill_button_bar_spacer_height"/>
 
   <java-symbol type="bool" name="autofill_dialog_horizontal_space_included"/>
 
@@ -5329,4 +5343,13 @@
   <java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
 
   <java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
+
+  <!-- System notification for satellite service -->
+  <java-symbol type="string" name="satellite_notification_title" />
+  <java-symbol type="string" name="satellite_notification_summary" />
+  <java-symbol type="string" name="satellite_notification_open_message" />
+  <java-symbol type="string" name="satellite_notification_how_it_works" />
+  <java-symbol type="drawable" name="ic_satellite_alt_24px" />
+
+  <java-symbol type="bool" name="config_watchlistUseFileHashesCache" />
 </resources>
diff --git a/core/tests/BroadcastRadioTests/OWNERS b/core/tests/BroadcastRadioTests/OWNERS
index d2bdd64..51a85e4 100644
--- a/core/tests/BroadcastRadioTests/OWNERS
+++ b/core/tests/BroadcastRadioTests/OWNERS
@@ -1,3 +1,3 @@
 xuweilin@google.com
 oscarazu@google.com
-keunyoung@google.com
+ericjeong@google.com
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 296c451..262f167 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -31,6 +31,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.Manifest;
 import android.app.compat.CompatChanges;
 import android.graphics.Bitmap;
 import android.hardware.broadcastradio.ConfigFlag;
@@ -57,6 +58,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
 import com.android.server.broadcastradio.RadioServiceUserController;
@@ -171,6 +174,10 @@
 
     @Before
     public void setup() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                        Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+
         doReturn(true).when(() -> CompatChanges.isChangeEnabled(
                 eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
         doReturn(USER_ID_1).when(mUserHandleMock).getIdentifier();
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index f476799..ee1a4ac 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -66,6 +66,7 @@
         "android.view.accessibility.flags-aconfig-java",
         "androidx.core_core",
         "androidx.core_core-ktx",
+        "androidx.test.core",
         "androidx.test.espresso.core",
         "androidx.test.ext.junit",
         "androidx.test.runner",
@@ -90,6 +91,7 @@
         "flickerlib-parsers",
         "flickerlib-trace_processor_shell",
         "mockito-target-extended-minus-junit4",
+        "TestParameterInjector",
     ],
 
     libs: [
@@ -203,12 +205,12 @@
         "androidx.test.uiautomator_uiautomator",
         "compatibility-device-util-axt",
         "flag-junit",
-        "mockito_ravenwood",
         "platform-test-annotations",
         "flag-junit",
         "testng",
     ],
     srcs: [
+        "src/android/app/ActivityManagerTest.java",
         "src/android/content/pm/PackageManagerTest.java",
         "src/android/content/pm/UserInfoTest.java",
         "src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
new file mode 100644
index 0000000..dcffe75
--- /dev/null
+++ b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@android:color/white"/>
+    <foreground android:drawable="@android:color/black"/>
+    <monochrome android:drawable="@android:color/system_accent2_800"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
new file mode 100644
index 0000000..d930e4d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.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 android.app;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testSimple() throws Exception {
+        assertTrue(ActivityManager.isSystemReady());
+        assertFalse(ActivityManager.isUserAMonkey());
+        assertNotEquals(UserHandle.USER_NULL, ActivityManager.getCurrentUser());
+    }
+
+    @Test
+    public void testCapabilities() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertNotNull(ActivityManager.getCapabilitiesSummary(~0));
+        ActivityManager.printCapabilitiesFull(new PrintWriter(new ByteArrayOutputStream()), ~0);
+        ActivityManager.printCapabilitiesSummary(new PrintWriter(new ByteArrayOutputStream()), ~0);
+        ActivityManager.printCapabilitiesSummary(new StringBuilder(), ~0);
+    }
+
+    @Test
+    public void testProcState() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
+        assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
+        assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
+        assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
+        assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
+        assertFalse(ActivityManager.isProcStateConsideredInteraction(PROCESS_STATE_SERVICE));
+    }
+
+    @Test
+    public void testStartResult() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertTrue(ActivityManager.isStartResultSuccessful(50));
+        assertTrue(ActivityManager.isStartResultFatalError(-50));
+    }
+
+    @Test
+    public void testRestrictionLevel() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertNotNull(ActivityManager.restrictionLevelToName(
+                ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/LaunchCookieTest.java b/core/tests/coretests/src/android/app/LaunchCookieTest.java
new file mode 100644
index 0000000..9325391
--- /dev/null
+++ b/core/tests/coretests/src/android/app/LaunchCookieTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.app.ActivityOptions.LaunchCookie;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LaunchCookieTest {
+
+    @Test
+    public void parcelNonNullLaunchCookie() {
+        LaunchCookie launchCookie = new LaunchCookie();
+        Parcel parcel = Parcel.obtain();
+        LaunchCookie.writeToParcel(launchCookie, parcel);
+        parcel.setDataPosition(0);
+        LaunchCookie unparceledLaunchCookie = LaunchCookie.readFromParcel(parcel);
+        assertEquals(launchCookie, unparceledLaunchCookie);
+    }
+
+    @Test
+    public void parcelNullLaunchCookie() {
+        Parcel parcel = Parcel.obtain();
+        LaunchCookie.writeToParcel(/*launchCookie*/null, parcel);
+        LaunchCookie unparceledLaunchCookie = LaunchCookie.readFromParcel(parcel);
+        assertNull(unparceledLaunchCookie);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index d8305f0..56ab034 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -19,6 +19,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
@@ -42,6 +45,8 @@
 import android.os.Parcel;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.MediaStore.Audio.AudioColumns;
 import android.test.mock.MockContentResolver;
 import android.util.Xml;
@@ -55,6 +60,7 @@
 import com.google.common.base.Strings;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -63,10 +69,15 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NotificationChannelTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private final String CLASS = "android.app.NotificationChannel";
 
     Context mContext;
@@ -294,28 +305,12 @@
         NotificationChannel channel = new NotificationChannel("id", "name", 3);
         channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
 
-        TypedXmlSerializer serializer = Xml.newFastSerializer();
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
-        serializer.startDocument(null, true);
-
         // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
         when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
                 .thenReturn(uriToBeRestoredCanonicalized);
         when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
                 .thenReturn(uriToBeRestoredCanonicalized);
 
-        channel.writeXmlForBackup(serializer, mContext);
-        serializer.endDocument();
-        serializer.flush();
-
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        byte[] byteArray = baos.toByteArray();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
-        parser.nextTag();
-
-        NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
-
         MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
         cursor.addRow(new Object[] {100L});
 
@@ -350,7 +345,263 @@
         when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
                 .thenReturn(uriAfterRestoredCanonicalized);
 
-        targetChannel.populateFromXmlForRestore(parser, true, mContext);
-        assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+        NotificationChannel restoredChannel = backUpAndRestore(channel);
+        assertThat(restoredChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+    }
+
+    @Test
+    public void testVibrationGetters_nonPatternBasedVibrationEffect_waveform() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        // Note that the amplitude used (1) is not the default amplitude, meaning that this effect
+        // does not have an equivalent pattern based effect.
+        VibrationEffect effect = VibrationEffect.createOneShot(123, 1);
+
+        channel.setVibrationEffect(effect);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+            assertNull(testedChannel.getVibrationPattern());
+            assertTrue(testedChannel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_nonPatternBasedVibrationEffect_nonWaveform() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        VibrationEffect effect =
+                VibrationEffect
+                        .startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose();
+
+        channel.setVibrationEffect(effect);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+            assertNull(testedChannel.getVibrationPattern()); // amplitude not default.
+            assertTrue(testedChannel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_patternBasedVibrationEffect_nonRepeating() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        long[] pattern = new long[] {1, 2};
+        VibrationEffect effect = VibrationEffect.createWaveform(pattern, /* repeatIndex= */ -1);
+
+        channel.setVibrationEffect(effect);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+            assertTrue(Arrays.equals(pattern, testedChannel.getVibrationPattern()));
+            assertTrue(testedChannel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_patternBasedVibrationEffect_wholeRepeating() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        long[] pattern = new long[] {1, 2};
+        VibrationEffect effect = VibrationEffect.createWaveform(pattern, /* repeatIndex= */ 0);
+
+        channel.setVibrationEffect(effect);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+            assertNull(testedChannel.getVibrationPattern());
+            assertTrue(testedChannel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_patternBasedVibrationEffect_partialRepeating()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        long[] pattern = new long[] {1, 2, 3, 4};
+        VibrationEffect effect = VibrationEffect.createWaveform(pattern, /* repeatIndex= */ 2);
+
+        channel.setVibrationEffect(effect);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertThat(testedChannel.getVibrationEffect()).isEqualTo(effect);
+            assertNull(testedChannel.getVibrationPattern());
+            assertTrue(testedChannel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_nullVibrationEffect() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        channel.setVibrationEffect(null);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertNull(channel.getVibrationEffect());
+            assertNull(channel.getVibrationPattern());
+            assertFalse(channel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_nullPattern() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        channel.setVibrationPattern(null);
+
+        Consumer<NotificationChannel> assertions = (testedChannel) -> {
+            assertThat(testedChannel.getVibrationEffect()).isNull();
+            assertNull(testedChannel.getVibrationPattern());
+            assertFalse(testedChannel.shouldVibrate());
+        };
+        assertions.accept(channel);
+        assertions.accept(writeToAndReadFromParcel(channel));
+        assertions.accept(backUpAndRestore(channel));
+    }
+
+    @Test
+    public void testVibrationGetters_setEffectOverridesSetPattern() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        VibrationEffect effect =
+                VibrationEffect.createOneShot(123, VibrationEffect.DEFAULT_AMPLITUDE);
+
+        channel.setVibrationPattern(new long[] {60, 80});
+        channel.setVibrationEffect(effect);
+
+        assertThat(channel.getVibrationEffect()).isEqualTo(effect);
+        assertTrue(Arrays.equals(new long[] {0, 123}, channel.getVibrationPattern()));
+        assertTrue(channel.shouldVibrate());
+
+        channel.setVibrationEffect(null);
+
+        assertNull(channel.getVibrationEffect());
+        assertNull(channel.getVibrationPattern());
+        assertFalse(channel.shouldVibrate());
+    }
+
+    @Test
+    public void testVibrationGetters_setPatternOverridesSetEffect() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        long[] pattern = new long[] {0, 123};
+
+        channel.setVibrationEffect(
+                VibrationEffect.createOneShot(123, VibrationEffect.DEFAULT_AMPLITUDE));
+        channel.setVibrationPattern(pattern);
+
+        assertThat(channel.getVibrationEffect())
+                .isEqualTo(VibrationEffect.createWaveform(pattern, -1));
+        assertTrue(Arrays.equals(pattern, channel.getVibrationPattern()));
+        assertTrue(channel.shouldVibrate());
+
+        channel.setVibrationPattern(null);
+
+        assertNull(channel.getVibrationEffect());
+        assertNull(channel.getVibrationPattern());
+        assertFalse(channel.shouldVibrate());
+    }
+
+    @Test
+    public void testEqualityDependsOnVibration() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel1 = new NotificationChannel("id", "name", 3);
+        NotificationChannel channel2 = new NotificationChannel("id", "name", 3);
+        assertThat(channel1).isEqualTo(channel2);
+
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP);
+        long[] pattern = new long[] {1, 2, 3};
+        channel1.setVibrationEffect(effect);
+        channel2.setVibrationEffect(effect);
+        assertThat(channel1).isEqualTo(channel2);
+
+        channel1.setVibrationPattern(pattern);
+        channel2.setVibrationPattern(pattern);
+        assertThat(channel1).isEqualTo(channel2);
+
+        channel1.setVibrationPattern(pattern);
+        channel2.setVibrationEffect(VibrationEffect.createWaveform(pattern, /* repeat= */ -1));
+        // Channels should still be equal, because the pattern and the effect set are equivalent.
+        assertThat(channel1).isEqualTo(channel2);
+
+        channel1.setVibrationEffect(effect);
+        channel2.setVibrationPattern(pattern);
+        assertThat(channel1).isNotEqualTo(channel2);
+    }
+
+    @Test
+    public void testSetVibrationPattern_flagOn_setsEffect() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        long[] pattern = new long[] {1, 2, 3};
+
+        channel.setVibrationPattern(pattern);
+
+        assertThat(channel.getVibrationEffect())
+                .isEqualTo(VibrationEffect.createWaveform(pattern, -1));
+    }
+
+    @Test
+    public void testSetVibrationPattern_flagNotOn_doesNotSetEffect() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        channel.setVibrationPattern(new long[] {1, 2, 3});
+
+        assertNull(channel.getVibrationEffect());
+    }
+
+    /** Backs up a given channel to an XML, and returns the channel read from the XML. */
+    private NotificationChannel backUpAndRestore(NotificationChannel channel) throws Exception {
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+
+        channel.writeXmlForBackup(serializer, mContext);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        byte[] byteArray = baos.toByteArray();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+        parser.nextTag();
+
+        NotificationChannel restoredChannel =
+                new NotificationChannel("default_id", "default_name", 3);
+        restoredChannel.populateFromXmlForRestore(parser, true, mContext);
+
+        return restoredChannel;
+    }
+
+    private NotificationChannel writeToAndReadFromParcel(NotificationChannel channel) {
+        Parcel parcel = Parcel.obtain();
+        channel.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return NotificationChannel.CREATOR.createFromParcel(parcel);
     }
 }
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 6e1c580..0aefef2 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -26,10 +26,14 @@
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.backup.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,7 +47,9 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BackupRestoreEventLoggerTest {
-    private static final int DATA_TYPES_ALLOWED = 15;
+    private static final int DATA_TYPES_ALLOWED_AFTER_FLAG = 150;
+
+    private static final int DATA_TYPES_ALLOWED_BEFORE_FLAG = 15;
 
     private static final String DATA_TYPE_1 = "data_type_1";
     private static final String DATA_TYPE_2 = "data_type_2";
@@ -55,6 +61,9 @@
     private BackupRestoreEventLogger mLogger;
     private MessageDigest mHashDigest;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mHashDigest = MessageDigest.getInstance("SHA-256");
@@ -83,10 +92,11 @@
     }
 
     @Test
-    public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+    public void testBackupLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
         mLogger = new BackupRestoreEventLogger(BACKUP);
 
-        for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+        for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
             String dataType = DATA_TYPE_1 + i;
             mLogger.logItemsBackedUp(dataType, /* count */ 5);
             mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
@@ -103,10 +113,53 @@
     }
 
     @Test
-    public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+    public void testRestoreLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
         mLogger = new BackupRestoreEventLogger(RESTORE);
 
-        for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+        for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
+            String dataType = DATA_TYPE_1 + i;
+            mLogger.logItemsRestored(dataType, /* count */ 5);
+            mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
+        }
+
+        mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void testBackupLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
+            String dataType = DATA_TYPE_1 + i;
+            mLogger.logItemsBackedUp(dataType, /* count */ 5);
+            mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logBackupMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
+        }
+
+        mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void testRestoreLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
             String dataType = DATA_TYPE_1 + i;
             mLogger.logItemsRestored(dataType, /* count */ 5);
             mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index a796a0f..c6447be 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -123,6 +123,7 @@
         final IBinder shareableActivityToken = new Binder();
         final int deviceId = 3;
         final IBinder taskFragmentToken = new Binder();
+        final IBinder initialCallerInfoAccessToken = new Binder();
 
         testRecycle(() -> new LaunchActivityItemBuilder(
                 activityToken, intent, activityInfo)
@@ -140,6 +141,7 @@
                 .setShareableActivityToken(shareableActivityToken)
                 .setTaskFragmentToken(taskFragmentToken)
                 .setDeviceId(deviceId)
+                .setInitialCallerInfoAccessToken(initialCallerInfoAccessToken)
                 .build());
     }
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 3823033..d641659 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -132,6 +132,8 @@
         private boolean mLaunchedFromBubble;
         @Nullable
         private IBinder mTaskFragmentToken;
+        @Nullable
+        private IBinder mInitialCallerInfoAccessToken;
 
         LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent,
                 @NonNull ActivityInfo info) {
@@ -251,13 +253,21 @@
         }
 
         @NonNull
+        LaunchActivityItemBuilder setInitialCallerInfoAccessToken(
+                @Nullable IBinder initialCallerInfoAccessToken) {
+            mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
+            return this;
+        }
+
+        @NonNull
         LaunchActivityItem build() {
             return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
                     mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
                     mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
                     mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */,
-                    mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken);
+                    mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken,
+                    mInitialCallerInfoAccessToken);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 952cdd9..508c6b2 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -189,6 +189,7 @@
                 .setAssistToken(new Binder())
                 .setShareableActivityToken(new Binder())
                 .setTaskFragmentToken(new Binder())
+                .setInitialCallerInfoAccessToken(new Binder())
                 .build();
 
         writeAndPrepareForReading(item);
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 36bb8e5..548b8ec 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -104,7 +104,9 @@
     private void createComplexDatabase() {
         mDatabase.beginTransaction();
         try {
-            mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+            // Column "l" is used to test the long variants.  The underlying sqlite type is int,
+            // which is the same as a java long.
+            mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text, l int);");
             mDatabase.setTransactionSuccessful();
         } finally {
             mDatabase.endTransaction();
@@ -115,7 +117,7 @@
      * A three-value insert for the complex database.
      */
     private String createComplexInsert() {
-        return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+        return "INSERT INTO t1 (i, d, t, l) VALUES (?1, ?2, ?3, ?4)";
     }
 
     /**
@@ -209,22 +211,25 @@
         mDatabase.beginTransaction();
         try {
             try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
-                for (int i = 0; i < 9; i++) {
-                    int vi = i * 3;
-                    double vd = i * 2.5;
-                    String vt = String.format("text%02dvalue", i);
+                for (int row = 0; row < 9; row++) {
+                    int vi = row * 3;
+                    double vd = row * 2.5;
+                    String vt = String.format("text%02dvalue", row);
+                    long vl = Long.MAX_VALUE - row;
                     s.bindInt(1, vi);
                     s.bindDouble(2, vd);
                     s.bindText(3, vt);
+                    s.bindLong(4, vl);
                     boolean r = s.step();
                     // No row is returned by this query.
                     assertFalse(r);
                     s.reset();
                 }
-                // The last row has a null double and a null text.
+                // The last row has a null double, null text, and null long.
                 s.bindInt(1, 20);
                 s.bindNull(2);
                 s.bindNull(3);
+                s.bindNull(4);
                 assertFalse(s.step());
                 s.reset();
             }
@@ -248,19 +253,31 @@
             mDatabase.endTransaction();
         }
 
-        // Verify that the element created with i == 3 is correct.
+        // Verify that the element created with row == 3 is correct.
         mDatabase.beginTransactionReadOnly();
         try {
-            final String query = "SELECT i, d, t FROM t1 WHERE t = 'text03value'";
+            final String query = "SELECT i, d, t, l FROM t1 WHERE t = 'text03value'";
             try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
                 assertTrue(s.step());
-                assertEquals(3, s.getResultColumnCount());
+                assertEquals(4, s.getResultColumnCount());
                 int vi = s.getColumnInt(0);
                 double vd = s.getColumnDouble(1);
                 String vt = s.getColumnText(2);
-                assertEquals(3 * 3, vi);
-                assertEquals(2.5 * 3, vd, 0.1);
+                long vl = s.getColumnLong(3);
+                // The query extracted the third generated row.
+                final int row = 3;
+                assertEquals(3 * row, vi);
+                assertEquals(2.5 * row, vd, 0.1);
                 assertEquals("text03value", vt);
+                assertEquals(Long.MAX_VALUE - row, vl);
+
+                // Verify the column types.  Remember that sqlite integers are the same as Java
+                // long, so the integer and long columns have type INTEGER.
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_FLOAT, s.getColumnType(1));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(2));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(3));
+
                 // No more rows.
                 assertFalse(s.step());
             }
@@ -268,15 +285,24 @@
             mDatabase.endTransaction();
         }
 
+        // Verify that null columns are returned properly.
         mDatabase.beginTransactionReadOnly();
         try {
-            final String query = "SELECT i, d, t FROM t1 WHERE i == 20";
+            final String query = "SELECT i, d, t, l FROM t1 WHERE i == 20";
             try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
                 assertTrue(s.step());
-                assertEquals(3, s.getResultColumnCount());
+                assertEquals(4, s.getResultColumnCount());
                 assertEquals(20, s.getColumnInt(0));
                 assertEquals(0.0, s.getColumnDouble(1), 0.01);
                 assertEquals(null, s.getColumnText(2));
+                assertEquals(0, s.getColumnLong(3));
+
+                // Verify the column types.
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(1));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(2));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(3));
+
                 // No more rows.
                 assertFalse(s.step());
             }
@@ -495,6 +521,8 @@
                 // Fetch the entire reference array.
                 s.bindInt(1, 1);
                 assertTrue(s.step());
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_BLOB, s.getColumnType(0));
+
                 byte[] a = s.getColumnBlob(0);
                 assertTrue(Arrays.equals(src, a));
                 s.reset();
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
deleted file mode 100644
index c8be191..0000000
--- a/core/tests/coretests/src/android/ddm/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-michschn@google.com
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 49ed3a8..950925f 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,8 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.IUriGrantsManager;
 import android.content.ContentProvider;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
@@ -32,13 +37,15 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
-import android.test.AndroidTestCase;
 import android.util.Log;
 
-import androidx.test.filters.SmallTest;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.frameworks.coretests.R;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -46,13 +53,29 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-public class IconTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class IconTest {
     public static final String TAG = IconTest.class.getSimpleName();
+    private Context mContext;
+
     public static void L(String s, Object... parts) {
         Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
     }
 
-    @SmallTest
+    private Context getContext() {
+        return mContext;
+    }
+
+    @Before
+    public void setup() {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
     public void testWithBitmap() throws Exception {
         final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
         final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
@@ -119,7 +142,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownIfNecessary() throws Exception {
         final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
         final Icon ic = Icon.createWithBitmap(bm);
@@ -132,7 +155,7 @@
         assertThat(ic.getBitmap().getHeight()).isLessThan(21);
     }
 
-    @SmallTest
+    @Test
     public void testWithAdaptiveBitmap() throws Exception {
         final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
 
@@ -166,7 +189,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testWithBitmapResource() throws Exception {
         final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -193,7 +216,7 @@
      * Icon resource test that ensures we can load and draw non-bitmaps. (In this case,
      * stat_sys_adb is assumed, and asserted, to be a vector drawable.)
      */
-    @SmallTest
+    @Test
     public void testWithStatSysAdbResource() throws Exception {
         // establish reference bitmap
         final float dp = getContext().getResources().getDisplayMetrics().density;
@@ -244,7 +267,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testWithFile() throws Exception {
         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -268,7 +291,55 @@
         }
     }
 
-    @SmallTest
+    @Test
+    public void testWithAdaptiveIconResource_useMonochrome() throws Exception {
+        final int colorMono = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.system_accent2_800)).getColor();
+        final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+                R.drawable.adaptiveicon_drawable, true, 0.0f);
+        final Drawable draw1 = im1.loadDrawable(mContext);
+        assertThat(draw1 instanceof InsetDrawable).isTrue();
+        ColorDrawable colorDrawable = (ColorDrawable) ((DrawableWrapper) draw1).getDrawable();
+        assertThat(colorDrawable.getColor()).isEqualTo(colorMono);
+    }
+
+    @Test
+    public void testWithAdaptiveIconResource_dontUseMonochrome() throws Exception {
+        final int colorMono = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.system_accent2_800)).getColor();
+        final int colorFg = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.black)).getColor();
+        final int colorBg = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.white)).getColor();
+
+        final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+                R.drawable.adaptiveicon_drawable, false , 0.0f);
+        final Drawable draw1 = im1.loadDrawable(mContext);
+        assertThat(draw1 instanceof AdaptiveIconDrawable).isTrue();
+        ColorDrawable colorDrawableMono = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+                .getMonochrome();
+        assertThat(colorDrawableMono.getColor()).isEqualTo(colorMono);
+        ColorDrawable colorDrawableFg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+                .getForeground();
+        assertThat(colorDrawableFg.getColor()).isEqualTo(colorFg);
+        ColorDrawable colorDrawableBg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+                .getBackground();
+        assertThat(colorDrawableBg.getColor()).isEqualTo(colorBg);
+    }
+
+    @Test
+    public void testAdaptiveIconResource_sameAs(@TestParameter boolean useMonochrome)
+            throws Exception {
+        final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+                R.drawable.adaptiveicon_drawable, useMonochrome, 1.0f);
+        final Parcel parcel = Parcel.obtain();
+        im1.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        final Icon im2 = Icon.CREATOR.createFromParcel(parcel);
+        assertThat(im1.sameAs(im2)).isTrue();
+    }
+
+    @Test
     public void testAsync() throws Exception {
         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -311,7 +382,7 @@
         L(TAG, "asyncTest: done");
     }
 
-    @SmallTest
+    @Test
     public void testParcel() throws Exception {
         final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -391,7 +462,7 @@
         return (int) Math.sqrt(maxNumPixels / aspRatio);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithBitmap() throws Exception {
         final int bmpWidth = 13_000;
         final int bmpHeight = 10_000;
@@ -408,7 +479,7 @@
         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
         final int bmpWidth = 20_000;
         final int bmpHeight = 10_000;
@@ -427,7 +498,7 @@
         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithResource() throws Exception {
         final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
@@ -435,7 +506,7 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithFile() throws Exception {
         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
                 .getBitmap();
@@ -450,7 +521,7 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithData() throws Exception {
         final int bmpBpp = 4;
         final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
@@ -465,7 +536,7 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
-    @SmallTest
+    @Test
     public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
         int uid = 12345;
         String packageName = "test_pkg";
@@ -509,7 +580,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
         int uid = 12345;
         String packageName = "test_pkg";
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 0bac1c7..3237812 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -28,15 +28,20 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public class HandlerThreadTest {
     private static final int TEST_WHAT = 1;
 
-    @Rule
+    @Rule(order = 1)
+    public ExpectedException mThrown = ExpectedException.none();
+
+    @Rule(order = 2)
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private boolean mGotMessage = false;
@@ -112,4 +117,28 @@
         assertTrue(mGotMessage);
         assertEquals(TEST_WHAT, mGotMessageWhat);
     }
+
+    /**
+     * Confirm that a background handler thread throwing an exception during a test results in a
+     * test failure being reported.
+     */
+    @Test
+    public void testUncaughtExceptionFails() throws Exception {
+        // For the moment we can only test Ravenwood; on a physical device uncaught exceptions
+        // are detected, but reported as test failures at a higher level where we can't inspect
+        Assume.assumeTrue(false); // TODO: re-enable
+        mThrown.expect(IllegalStateException.class);
+
+        final HandlerThread thread = new HandlerThread("HandlerThreadTest");
+        thread.start();
+        thread.getThreadHandler().post(() -> {
+            throw new IllegalStateException();
+        });
+
+        // Wait until we've drained past the message above, then terminate test without throwing
+        // directly; the test harness should notice and report the uncaught exception
+        while (!thread.getThreadHandler().getLooper().getQueue().isIdle()) {
+            SystemClock.sleep(10);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index a28bb69..2bd5631 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -219,6 +219,22 @@
             workDuration.setActualGpuDurationNanos(6);
             s.reportActualWorkDuration(workDuration);
         }
+        {
+            WorkDuration workDuration = new WorkDuration();
+            workDuration.setWorkPeriodStartTimestampNanos(1);
+            workDuration.setActualTotalDurationNanos(14);
+            workDuration.setActualCpuDurationNanos(0);
+            workDuration.setActualGpuDurationNanos(6);
+            s.reportActualWorkDuration(workDuration);
+        }
+        {
+            WorkDuration workDuration = new WorkDuration();
+            workDuration.setWorkPeriodStartTimestampNanos(1);
+            workDuration.setActualTotalDurationNanos(14);
+            workDuration.setActualCpuDurationNanos(7);
+            workDuration.setActualGpuDurationNanos(0);
+            s.reportActualWorkDuration(workDuration);
+        }
     }
 
     @Test
@@ -242,7 +258,7 @@
             s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6, 1));
+            s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 0, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
             s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1, 1));
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
new file mode 100644
index 0000000..f5a81c5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VibrationAttributesTest {
+    @Test
+    public void testSimple() throws Exception {
+        final VibrationAttributes attr = new VibrationAttributes.Builder()
+                .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                .setUsage(VibrationAttributes.USAGE_ALARM)
+                .build();
+
+        assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory());
+        assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage());
+    }
+}
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
index a525615..c18f35f 100644
--- a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -126,4 +126,4 @@
 
         TextLine.recycle(tl)
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
index 27869bb..71980c1 100644
--- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -21,7 +21,7 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -40,7 +40,7 @@
     @JvmField
     val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun calculateRunFlagTest() {
         // Only one Bidi run
@@ -84,7 +84,7 @@
                 .isEqualTo(LEFT_EDGE)
     }
 
-    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun resolveRunFlagForSubSequenceTest() {
         val runStart = 5
@@ -221,4 +221,4 @@
                 MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
                 .isEqualTo(MIDDLE_OF_LINE)
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
index bd2f36f..d57f1fc 100644
--- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -658,6 +658,52 @@
         Truth.assertThat(matchingPackets).hasSize(1);
     }
 
+    @Test
+    public void canTraceOnFlush() throws InvalidProtocolBufferException, InterruptedException {
+        final int singleIntValue = 101;
+        sInstanceProvider = (ds, idx, config) ->
+                new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {},
+                        (args) -> sTestDataSource.trace(ctx -> {
+                            final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                            long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                            long payloadToken = protoOutputStream.start(PAYLOAD);
+                            protoOutputStream.write(SINGLE_INT, singleIntValue);
+                            protoOutputStream.end(payloadToken);
+                            protoOutputStream.end(forTestingToken);
+
+                            ctx.flush();
+                        }),
+                        (args) -> {}
+                );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt()
+                        == singleIntValue).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
     interface RunnableCreator {
         Runnable create(int state, AtomicInteger stateOut);
     }
diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/view/ViewDebugTest.java
similarity index 97%
rename from core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
rename to core/tests/coretests/src/android/view/ViewDebugTest.java
index 7248983..4522842 100644
--- a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
+++ b/core/tests/coretests/src/android/view/ViewDebugTest.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package android.ddm;
+package android.view;
 
-import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
-import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
+import static android.view.ViewDebug.deserializeMethodParameters;
+import static android.view.ViewDebug.serializeReturnValue;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 
-import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
 import android.platform.test.annotations.Presubmit;
+import android.view.ViewDebug.ViewMethodInvocationSerializationException;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -39,7 +39,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
-public final class DdmHandleViewDebugTest {
+public final class ViewDebugTest {
     // true
     private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
 
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..52e996c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,7 +18,9 @@
 
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -475,8 +477,9 @@
      * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
      */
     @Test
-    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+    public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
         View view = new View(sContext);
         attachViewToWindow(view);
         ViewRootImpl viewRootImpl = view.getViewRootImpl();
@@ -507,8 +510,9 @@
      * <7%: FRAME_RATE_CATEGORY_LOW
      */
     @Test
-    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+    public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
         View view = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -534,8 +538,9 @@
      * >=7% : FRAME_RATE_CATEGORY_NORMAL
      */
     @Test
-    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+    public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
         View view = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -559,6 +564,96 @@
     }
 
     /**
+     * Test the value of the frame rate cateogry based on the visibility of a view
+     * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+     * Visible: FRAME_RATE_CATEGORY_HIGH
+     * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
+        View view = new View(sContext);
+        attachViewToWindow(view);
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.setVisibility(View.INVISIBLE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        sInstrumentation.runOnMainSync(() -> {
+            view.setVisibility(View.VISIBLE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_HIGH);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+        });
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the size of a view.
+     * The current threshold value is 7% of the screen size
+     * <7%: FRAME_RATE_CATEGORY_NORMAL
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+        wmlp.width = 1;
+        wmlp.height = 1;
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+        });
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the size of a view.
+     * The current threshold value is 7% of the screen size
+     * >=7% : FRAME_RATE_CATEGORY_HIGH
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            Display display = wm.getDefaultDisplay();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            wmlp.width = (int) (metrics.widthPixels * 0.9);
+            wmlp.height = (int) (metrics.heightPixels * 0.9);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+    }
+
+    /**
      * Test how values of the frame rate cateogry are aggregated.
      * It should take the max value among all of the voted categories per frame.
      */
@@ -575,8 +670,13 @@
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_HIGH_HINT);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
@@ -701,6 +801,88 @@
         });
     }
 
+    /**
+     * Test votePreferredFrameRate_voteFrameRateTimeOut
+     * If no frame rate is voted in 100 milliseconds, the value of
+     * mPreferredFrameRate should be set to 0.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
+        final long delay = 200L;
+
+        View view = new View(sContext);
+        attachViewToWindow(view);
+        sInstrumentation.waitForIdleSync();
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+            viewRootImpl.votePreferredFrameRate(24);
+            assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+        });
+
+        Thread.sleep(delay);
+        assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+    }
+
+    /**
+     * Test the logic of infrequent layer:
+     * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+     * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+     * - otherwise, use the previous category value.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
+        final long delay = 200L;
+
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            Display display = wm.getDefaultDisplay();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            wmlp.width = (int) (metrics.widthPixels * 0.9);
+            wmlp.height = (int) (metrics.heightPixels * 0.9);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+        // Frequent update
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+
+        // In transistion from frequent update to infrequent update
+        Thread.sleep(delay);
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+
+        // Infrequent update
+        Thread.sleep(delay);
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+        });
+    }
+
     @Test
     public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
         mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 5862711..3d5494d 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -422,19 +422,9 @@
         if (!drawDataParcel()) {
             return;
         }
-        final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
-        final RemoteViews rv = new RemoteViews(drawInstructions);
-        final View view = rv.apply(mContext, mContainer);
-        assertTrue(view instanceof RemoteCanvas);
-        assertEquals(drawInstructions, view.getTag());
-    }
-
-    @Test
-    public void remoteCanvasWiresClickHandlers() {
-        if (!drawDataParcel()) {
-            return;
-        }
-        final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+        final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
+        final RemoteViews.DrawInstructions drawInstructions =
+                new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
         final RemoteViews rv = new RemoteViews(drawInstructions);
         final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
                 new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
@@ -443,15 +433,7 @@
         rv.setPendingIntentTemplate(viewId, pi);
         rv.setOnClickFillInIntent(viewId, i);
         final View view = rv.apply(mContext, mContainer);
-        assertTrue(view instanceof RemoteCanvas);
-        RemoteCanvas target = (RemoteCanvas) view;
-        assertEquals(1, target.getCallbacks().size());
-        assertNotNull(target.getCallbacks().get(viewId));
-    }
-
-    private RemoteViews.DrawInstructions getDrawInstructions() {
-        final byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
-        return new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
+        assertEquals(drawInstructions, view.getTag());
     }
 
     private RemoteViews createViewChained(int depth, String... texts) {
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 52ff0d4..a709d7b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,7 +20,6 @@
 import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeast;
@@ -359,7 +358,7 @@
     }
 
     @Test
-    public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
+    public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
 
         OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -369,12 +368,13 @@
         waitForIdle();
         verify(mCallback1).onBackStarted(any(BackEvent.class));
 
-        // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
+        // This should trigger mCallback1.onBackCancelled()
         mDispatcher.detachFromWindow();
+        // This should be ignored by mCallback1
+        callbackInfo.getCallback().onBackInvoked();
 
-        OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
-        assertNull(callbackInfo1);
         waitForIdle();
+        verify(mCallback1, never()).onBackInvoked();
         verify(mCallback1).onBackCancelled();
     }
 }
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 145aa60d..75b0d4a 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -21,6 +21,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
 import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
@@ -82,7 +83,6 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.AfterClass;
@@ -726,14 +726,14 @@
 
     private void configureNoShortcutService() throws Exception {
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
                 .thenReturn(Collections.emptyList());
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
     }
 
     private void configureValidShortcutService() throws Exception {
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
                 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
         Settings.Secure.putString(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
@@ -744,7 +744,7 @@
                 (ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
                         .keySet().toArray()[0];
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
                 .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                 featureComponentName.flattenToString());
@@ -806,7 +806,7 @@
 
     private void configureDefaultAccessibilityService() throws Exception {
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
                 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
 
         when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(
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 2ea044c..69b6a9b7a 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
@@ -39,7 +39,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.accessibility.TestUtils;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 
@@ -100,7 +99,7 @@
 
         mSut = new InvisibleToggleAccessibilityServiceTarget(
                 mContextSpy,
-                ShortcutConstants.UserShortcutType.HARDWARE, accessibilityServiceInfo);
+                AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY, accessibilityServiceInfo);
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
new file mode 100644
index 0000000..08333ec
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.flags.Flags;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests the consistency of {@link NotificationOptimizedLinearLayout}'s onMeasure and onLayout
+ * implementations with the behavior of the standard Android LinearLayout.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_NOTIF_LINEARLAYOUT_OPTIMIZED)
+@Presubmit
+public class NotificationOptimizedLinearLayoutComparisonTest {
+
+    @Rule
+    public final Expect mExpect = Expect.create();
+
+    private static final int[] ORIENTATIONS = {LinearLayout.VERTICAL, LinearLayout.HORIZONTAL};
+    private static final int EXACT_SPEC = MeasureSpec.makeMeasureSpec(500,
+            MeasureSpec.EXACTLY);
+    private static final int AT_MOST_SPEC = MeasureSpec.makeMeasureSpec(500,
+            MeasureSpec.AT_MOST);
+
+    private static final int[] MEASURE_SPECS = {EXACT_SPEC, AT_MOST_SPEC};
+
+    private static final int[] GRAVITIES =
+            {Gravity.NO_GRAVITY, Gravity.TOP, Gravity.LEFT, Gravity.CENTER};
+
+    private static final int[] LAYOUT_PARAMS = {MATCH_PARENT, WRAP_CONTENT, 0, 50};
+    private static final int[] CHILD_WEIGHTS = {0, 1};
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private Context mContext;
+
+    @Before
+    public void before() {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    public void test() throws Throwable {
+        for (int orientation : ORIENTATIONS) {
+            for (int widthSpec : MEASURE_SPECS) {
+                for (int heightSpec : MEASURE_SPECS) {
+                    for (int firstChildGravity : GRAVITIES) {
+                        for (int secondChildGravity : GRAVITIES) {
+                            for (int firstChildLayoutWidth : LAYOUT_PARAMS) {
+                                for (int firstChildLayoutHeight : LAYOUT_PARAMS) {
+                                    for (int secondChildLayoutWidth : LAYOUT_PARAMS) {
+                                        for (int secondChildLayoutHeight : LAYOUT_PARAMS) {
+                                            for (int firstChildWeight : CHILD_WEIGHTS) {
+                                                for (int secondChildWeight : CHILD_WEIGHTS) {
+                                                    executeTest(/*testSpec =*/createTestSpec(
+                                                            orientation,
+                                                            widthSpec, heightSpec,
+                                                            firstChildLayoutWidth,
+                                                            firstChildLayoutHeight,
+                                                            secondChildLayoutWidth,
+                                                            secondChildLayoutHeight,
+                                                            firstChildGravity,
+                                                            secondChildGravity,
+                                                            firstChildWeight,
+                                                            secondChildWeight));
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void executeTest(TestSpec testSpec) {
+        // GIVEN
+        final List<View> controlChildren =
+                new ArrayList<>();
+        final List<View> testChildren =
+                new ArrayList<>();
+
+        controlChildren.add(
+                buildChildView(
+                        testSpec.mFirstChildLayoutWidth,
+                        testSpec.mFirstChildLayoutHeight,
+                        testSpec.mFirstChildGravity,
+                        testSpec.mFirstChildWeight));
+        controlChildren.add(
+                buildChildView(
+                        testSpec.mSecondChildLayoutWidth,
+                        testSpec.mSecondChildLayoutHeight,
+                        testSpec.mSecondChildGravity,
+                        testSpec.mSecondChildWeight));
+
+        testChildren.add(
+                buildChildView(
+                        testSpec.mFirstChildLayoutWidth,
+                        testSpec.mFirstChildLayoutHeight,
+                        testSpec.mFirstChildGravity,
+                        testSpec.mFirstChildWeight));
+        testChildren.add(
+                buildChildView(
+                        testSpec.mSecondChildLayoutWidth,
+                        testSpec.mSecondChildLayoutHeight,
+                        testSpec.mSecondChildGravity,
+                        testSpec.mSecondChildWeight));
+
+        final LinearLayout controlContainer = buildLayout(false,
+                testSpec.mOrientation,
+                controlChildren);
+
+        final LinearLayout testContainer = buildLayout(true,
+                testSpec.mOrientation,
+                testChildren);
+
+        // WHEN
+        controlContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
+        testContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
+        controlContainer.layout(0, 0, 1000, 1000);
+        testContainer.layout(0, 0, 1000, 1000);
+        // THEN
+        assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer);
+    }
+
+    private static class TestSpec {
+        private final int mOrientation;
+        private final int mWidthSpec;
+        private final int mHeightSpec;
+        private final int mFirstChildLayoutWidth;
+        private final int mFirstChildLayoutHeight;
+        private final int mSecondChildLayoutWidth;
+        private final int mSecondChildLayoutHeight;
+        private final int mFirstChildGravity;
+        private final int mSecondChildGravity;
+        private final int mFirstChildWeight;
+        private final int mSecondChildWeight;
+
+        TestSpec(
+                int orientation,
+                int widthSpec,
+                int heightSpec,
+                int firstChildLayoutWidth,
+                int firstChildLayoutHeight,
+                int secondChildLayoutWidth,
+                int secondChildLayoutHeight,
+                int firstChildGravity,
+                int secondChildGravity,
+                int firstChildWeight,
+                int secondChildWeight) {
+            mOrientation = orientation;
+            mWidthSpec = widthSpec;
+            mHeightSpec = heightSpec;
+            mFirstChildLayoutWidth = firstChildLayoutWidth;
+            mFirstChildLayoutHeight = firstChildLayoutHeight;
+            mSecondChildLayoutWidth = secondChildLayoutWidth;
+            mSecondChildLayoutHeight = secondChildLayoutHeight;
+            mFirstChildGravity = firstChildGravity;
+            mSecondChildGravity = secondChildGravity;
+            mFirstChildWeight = firstChildWeight;
+            mSecondChildWeight = secondChildWeight;
+        }
+
+        @Override
+        public String toString() {
+            return "TestSpec{"
+                    + "mOrientation=" + orientationToString(mOrientation)
+                    + ", mWidthSpec=" + MeasureSpec.toString(mWidthSpec)
+                    + ", mHeightSpec=" + MeasureSpec.toString(mHeightSpec)
+                    + ", mFirstChildLayoutWidth=" + sizeToString(mFirstChildLayoutWidth)
+                    + ", mFirstChildLayoutHeight=" + sizeToString(mFirstChildLayoutHeight)
+                    + ", mSecondChildLayoutWidth=" + sizeToString(mSecondChildLayoutWidth)
+                    + ", mSecondChildLayoutHeight=" + sizeToString(mSecondChildLayoutHeight)
+                    + ", mFirstChildGravity=" + mFirstChildGravity
+                    + ", mSecondChildGravity=" + mSecondChildGravity
+                    + ", mFirstChildWeight=" + mFirstChildWeight
+                    + ", mSecondChildWeight=" + mSecondChildWeight
+                    + '}';
+        }
+
+        private String orientationToString(int orientation) {
+            if (orientation == LinearLayout.VERTICAL) {
+                return "vertical";
+            } else if (orientation == LinearLayout.HORIZONTAL) {
+                return "horizontal";
+            }
+            throw new IllegalArgumentException();
+        }
+
+        private String sizeToString(int size) {
+            if (size == WRAP_CONTENT) {
+                return "wrap-content";
+            }
+            if (size == MATCH_PARENT) {
+                return "match-parent";
+            }
+            return String.valueOf(size);
+        }
+    }
+
+    private LinearLayout buildLayout(boolean isNotificationOptimized,
+            @LinearLayout.OrientationMode int orientation, List<View> children) {
+        final LinearLayout linearLayout;
+        if (isNotificationOptimized) {
+            linearLayout = new NotificationOptimizedLinearLayout(mContext);
+        } else {
+            linearLayout = new LinearLayout(mContext);
+        }
+        linearLayout.setOrientation(orientation);
+        for (int i = 0; i < children.size(); i++) {
+            linearLayout.addView(children.get(i));
+        }
+        return linearLayout;
+    }
+
+    private void assertLayoutsEqual(String testCase, View controlView, View testView) {
+        mExpect.withMessage("MeasuredWidths are not equal. Test Case:" + testCase)
+                .that(testView.getMeasuredWidth()).isEqualTo(controlView.getMeasuredWidth());
+        mExpect.withMessage("MeasuredHeights are not equal. Test Case:" + testCase)
+                .that(testView.getMeasuredHeight()).isEqualTo(controlView.getMeasuredHeight());
+        mExpect.withMessage("Left Positions are not equal. Test Case:" + testCase)
+                .that(testView.getLeft()).isEqualTo(controlView.getLeft());
+        mExpect.withMessage("Top Positions are not equal. Test Case:" + testCase)
+                .that(testView.getTop()).isEqualTo(controlView.getTop());
+        if (controlView instanceof ViewGroup && testView instanceof ViewGroup) {
+            final ViewGroup controlGroup = (ViewGroup) controlView;
+            final ViewGroup testGroup = (ViewGroup) testView;
+            // Test and Control Views should be identical by hierarchy for the comparison.
+            // That's why mExpect is not used here for assertion.
+            assertEquals(controlGroup.getChildCount(), testGroup.getChildCount());
+
+            for (int i = 0; i < controlGroup.getChildCount(); i++) {
+                View controlChild = controlGroup.getChildAt(i);
+                View testChild = testGroup.getChildAt(i);
+
+                assertLayoutsEqual(testCase, controlChild, testChild);
+            }
+        }
+    }
+
+    private static class TestView extends View {
+        TestView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public int getBaseline() {
+            return 5;
+        }
+    }
+
+
+    private TestSpec createTestSpec(int orientation,
+            int widthSpec, int heightSpec,
+            int firstChildLayoutWidth, int firstChildLayoutHeight, int secondChildLayoutWidth,
+            int secondChildLayoutHeight, int firstChildGravity, int secondChildGravity,
+            int firstChildWeight, int secondChildWeight) {
+
+        return new TestSpec(
+                orientation,
+                widthSpec, heightSpec,
+                firstChildLayoutWidth,
+                firstChildLayoutHeight,
+                secondChildLayoutWidth,
+                secondChildLayoutHeight,
+                firstChildGravity,
+                secondChildGravity,
+                firstChildWeight,
+                secondChildWeight);
+    }
+
+    private View buildChildView(int childLayoutWidth, int childLayoutHeight,
+            int childGravity, int childWeight) {
+        final View childView = new TestView(mContext);
+        // Set desired size using LayoutParams
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childLayoutWidth,
+                childLayoutHeight, childWeight);
+        params.gravity = childGravity;
+        childView.setLayoutParams(params);
+        return childView;
+    }
+}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 3e0e36d..39cb616 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -353,7 +353,8 @@
                     null /* pendingResults */, null /* pendingNewIntents */,
                     null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
                     mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
-                    false /* launchedFromBubble */, null /* taskfragmentToken */);
+                    false /* launchedFromBubble */, null /* taskfragmentToken */,
+                    null /* initialCallerInfoAccessToken */);
         }
 
         @Override
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 2ccee71..f5563a7 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -60,7 +60,6 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.rules",
-        "mockito_ravenwood",
         "frameworks-base-testutils",
         "servicestests-utils",
     ],
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index ade20d2..1fd1003 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -66,6 +66,12 @@
     src: "preinstalled-packages-strict-signature.xml",
 }
 
+prebuilt_etc {
+    name: "enhanced-confirmation.xml",
+    sub_dir: "sysconfig",
+    src: "enhanced-confirmation.xml",
+}
+
 // Privapp permission whitelist files
 
 prebuilt_etc {
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
new file mode 100644
index 0000000..3b1867c
--- /dev/null
+++ b/data/etc/enhanced-confirmation.xml
@@ -0,0 +1,56 @@
+<?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.
+ -->
+
+<!--
+This XML defines an allowlist of packages that should be exempt from ECM (Enhanced Confirmation
+Mode).
+
+Example usage:
+
+    <enhanced-confirmation-trusted-package
+         package="com.example.app"
+         sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+    ...
+
+    <enhanced-confirmation-trusted-installer
+         package="com.example.installer"
+         sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+    ...
+
+The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
+should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+
+The "enhanced-confirmation-trusted-installer" entry shown above indicates that
+"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
+packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
+For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
+restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+
+In either case:
+
+- The "package" XML attribute refers to the app's package name.
+- The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
+
+For any entry to successfully apply to a package, both XML attributes must be present, and must
+match the package. That is, the package name must match the "package" attribute, and the app must be
+signed by the signing certificate identified by the "sha256-cert-digest" attribute..
+-->
+
+<config></config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2873428..0baaff0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,8 @@
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
         <permission name="android.permission.UWB_PRIVILEGED"/>
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+        <permission name="android.permission.UPDATE_CONFIG"/>
+        <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.calendar">
@@ -571,6 +573,8 @@
         <!-- Permission required for BinaryTransparencyService shell API and host test -->
         <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+        <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+        <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index ae61a2d..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -293,7 +293,7 @@
      * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
      * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
 
 
@@ -324,7 +324,7 @@
      * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
      * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
 
     // These flags are always set on a new/reset paint, even if flags 0 is passed.
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index dd82fed..50b167e 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -489,7 +489,7 @@
             @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative");
         try {
-            if (Flags.toolkitSetFrameRate()) {
+            if (Flags.toolkitSetFrameRateReadOnly()) {
                 SurfaceTexture st = weakSelf.get();
                 if (st != null) {
                     Handler handler = st.mOnSetFrameRateHandler;
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 45e29a8..f359025 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -154,6 +154,12 @@
     // TYPE_DATA: data offset
     private int             mInt2;
 
+    // TYPE_RESOURCE: use the monochrome drawable from an AdaptiveIconDrawable
+    private boolean mUseMonochrome = false;
+
+    // TYPE_RESOURCE: wrap the monochrome drawable in an InsetDrawable with the specified inset
+    private float mInsetScale = 0.0f;
+
     /**
      * Gets the type of the icon provided.
      * <p>
@@ -368,10 +374,34 @@
             result.setTintList(mTintList);
             result.setTintBlendMode(mBlendMode);
         }
+
+        if (mUseMonochrome) {
+            return crateMonochromeDrawable(result, mInsetScale);
+        }
+
         return result;
     }
 
     /**
+     * Gets the monochrome drawable from an {@link AdaptiveIconDrawable}.
+     *
+     * @param drawable An {@link AdaptiveIconDrawable}
+     * @return Adjusted (wrapped in {@link InsetDrawable}) monochrome drawable
+     *  from an {@link AdaptiveIconDrawable}.
+     * Or the original drawable if no monochrome layer exists.
+     */
+    private static Drawable crateMonochromeDrawable(Drawable drawable, float inset) {
+        if (drawable instanceof AdaptiveIconDrawable) {
+            Drawable monochromeDrawable = ((AdaptiveIconDrawable) drawable).getMonochrome();
+            // wrap with negative inset => scale icon (inspired from BaseIconFactory)
+            if (monochromeDrawable != null) {
+                return new InsetDrawable(monochromeDrawable, inset);
+            }
+        }
+        return drawable;
+    }
+
+    /**
      * Resizes image if size too large for Canvas to draw
      * @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
      * @return resized bitmap
@@ -693,7 +723,9 @@
                         && Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
             case TYPE_RESOURCE:
                 return getResId() == otherIcon.getResId()
-                        && Objects.equals(getResPackage(), otherIcon.getResPackage());
+                        && Objects.equals(getResPackage(), otherIcon.getResPackage())
+                        && mUseMonochrome == otherIcon.mUseMonochrome
+                        && mInsetScale == otherIcon.mInsetScale;
             case TYPE_URI:
             case TYPE_URI_ADAPTIVE_BITMAP:
                 return Objects.equals(getUriString(), otherIcon.getUriString());
@@ -748,6 +780,26 @@
     }
 
     /**
+     * Create an Icon pointing to a drawable resource.
+     * @param resPackage Name of the package containing the resource in question
+     * @param resId ID of the drawable resource
+     * @param useMonochrome if this icon should use the monochrome res from the adaptive drawable
+     * @hide
+     */
+    public static @NonNull Icon createWithResourceAdaptiveDrawable(@NonNull String resPackage,
+            @DrawableRes int resId, boolean useMonochrome, float inset) {
+        if (resPackage == null) {
+            throw new IllegalArgumentException("Resource package name must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_RESOURCE);
+        rep.mInt1 = resId;
+        rep.mUseMonochrome = useMonochrome;
+        rep.mInsetScale = inset;
+        rep.mString1 = resPackage;
+        return rep;
+    }
+
+    /**
      * Create an Icon pointing to a bitmap in memory.
      * @param bits A valid {@link android.graphics.Bitmap} object
      */
@@ -986,6 +1038,8 @@
                 final int resId = in.readInt();
                 mString1 = pkg;
                 mInt1 = resId;
+                mUseMonochrome = in.readBoolean();
+                mInsetScale = in.readFloat();
                 break;
             case TYPE_DATA:
                 final int len = in.readInt();
@@ -1027,6 +1081,8 @@
             case TYPE_RESOURCE:
                 dest.writeString(getResPackage());
                 dest.writeInt(getResId());
+                dest.writeBoolean(mUseMonochrome);
+                dest.writeFloat(mInsetScale);
                 break;
             case TYPE_DATA:
                 dest.writeInt(getDataLength());
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 9707126..0e9f29d 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,7 +17,7 @@
 package android.graphics.text;
 
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -183,7 +183,7 @@
     /**
      * Value for justification mode indicating the text is justified by stretching letter spacing.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
 
     /**
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 2b95f30..9a66c0f 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -57,3 +57,10 @@
     description: "Enables new UMO experience for PiP menu"
     bug: "307998712"
 }
+
+flag {
+    name: "enable_bubble_bar"
+    namespace: "multitasking"
+    description: "Enables the new bubble bar UI for tablets"
+    bug: "286246694"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index f32f030..a2a2914 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -19,6 +19,7 @@
 import static android.os.AsyncTask.Status.FINISHED;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.annotation.DimenRes;
 import android.annotation.Hide;
@@ -47,6 +48,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -465,7 +467,8 @@
     /**
      * Call when all the views should be removed/cleaned up.
      */
-    void cleanupViews() {
+    public void cleanupViews() {
+        ProtoLog.d(WM_SHELL_BUBBLES, "Bubble#cleanupViews=%s", getKey());
         cleanupViews(true);
     }
 
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 aea3ca1..0aa8959 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
@@ -23,7 +23,6 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -259,7 +258,7 @@
     /** One handed mode controller to register transition listener. */
     private final Optional<OneHandedController> mOneHandedOptional;
     /** Drag and drop controller to register listener for onDragStarted. */
-    private final Optional<DragAndDropController> mDragAndDropController;
+    private final DragAndDropController mDragAndDropController;
     /** Used to send bubble events to launcher. */
     private Bubbles.BubbleStateListener mBubbleStateListener;
 
@@ -285,7 +284,7 @@
             BubblePositioner positioner,
             DisplayController displayController,
             Optional<OneHandedController> oneHandedOptional,
-            Optional<DragAndDropController> dragAndDropController,
+            DragAndDropController dragAndDropController,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
@@ -435,6 +434,9 @@
                     boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
                 for (Bubble b : mBubbleData.getBubbles()) {
                     if (task.taskId == b.getTaskId()) {
+                        ProtoLog.d(WM_SHELL_BUBBLES,
+                                "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
+                                task.taskId, b.getKey());
                         mBubbleData.setSelectedBubble(b);
                         mBubbleData.setExpanded(true);
                         return;
@@ -442,6 +444,9 @@
                 }
                 for (Bubble b : mBubbleData.getOverflowBubbles()) {
                     if (task.taskId == b.getTaskId()) {
+                        ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
+                                        + "selecting matching overflow bubble=%s",
+                                task.taskId, b.getKey());
                         promoteBubbleFromOverflow(b);
                         mBubbleData.setExpanded(true);
                         return;
@@ -463,7 +468,7 @@
                 });
 
         mOneHandedOptional.ifPresent(this::registerOneHandedState);
-        mDragAndDropController.ifPresent(controller -> controller.addListener(this::collapseStack));
+        mDragAndDropController.addListener(this::collapseStack);
 
         // Clear out any persisted bubbles on disk that no longer have a valid user.
         List<UserInfo> users = mUserManager.getAliveUsers();
@@ -581,10 +586,15 @@
             // Hide the stack temporarily if the status bar has been made invisible, and the stack
             // is collapsed. An expanded stack should remain visible until collapsed.
             mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
+            ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b",
+                    visible, isStackExpanded());
         }
     }
 
     private void onZenStateChanged() {
+        if (hasBubbles()) {
+            ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged");
+        }
         for (Bubble b : mBubbleData.getBubbles()) {
             b.setShowDot(b.showInShade());
         }
@@ -593,9 +603,10 @@
     @VisibleForTesting
     public void onStatusBarStateChanged(boolean isShade) {
         boolean didChange = mIsStatusBarShade != isShade;
-        if (DEBUG_BUBBLE_CONTROLLER) {
-            Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged "
+                        + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s",
+                isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null
+                        ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null"));
         mIsStatusBarShade = isShade;
         if (!mIsStatusBarShade && didChange) {
             // Only collapse stack on change
@@ -611,6 +622,8 @@
 
     @VisibleForTesting
     public void onBubbleMetadataFlagChanged(Bubble bubble) {
+        ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d",
+                bubble.getKey(), bubble.getFlags());
         // Make sure NoMan knows suppression state so that anyone querying it can tell.
         try {
             mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
@@ -623,6 +636,8 @@
     /** Called when the current user changes. */
     @VisibleForTesting
     public void onUserChanged(int newUserId) {
+        ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d",
+                mCurrentUserId, newUserId);
         saveBubbles(mCurrentUserId);
         mCurrentUserId = newUserId;
 
@@ -730,7 +745,7 @@
             // window to show this in, but we use a separate code path.
             // TODO(b/273312602): consider foldables where we do need a stack view when folded
             if (mLayerView == null) {
-                mLayerView = new BubbleBarLayerView(mContext, this);
+                mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
                 mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
             }
         } else {
@@ -825,6 +840,7 @@
      */
     void updateWindowFlagsForBackpress(boolean interceptBack) {
         if (mAddedToWindowManager) {
+            ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack);
             mWmLayoutParams.flags = interceptBack
                     ? 0
                     : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -903,6 +919,9 @@
         if (mStackView != null) {
             mStackView.setVisibility(INVISIBLE);
             removeFromWindowManagerMaybe();
+        } else if (mLayerView != null) {
+            mLayerView.setVisibility(INVISIBLE);
+            removeFromWindowManagerMaybe();
         }
     }
 
@@ -1014,8 +1033,9 @@
     }
 
     private void onNotificationPanelExpandedChanged(boolean expanded) {
-        ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
         if (mStackView != null && mStackView.isExpanded()) {
+            ProtoLog.d(WM_SHELL_BUBBLES,
+                    "onNotificationPanelExpandedChanged expanded=%b", expanded);
             if (expanded) {
                 mStackView.stopMonitoringSwipeUpGesture();
             } else {
@@ -1096,6 +1116,7 @@
     /** Promote the provided bubble from the overflow view. */
     public void promoteBubbleFromOverflow(Bubble bubble) {
         mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+        ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
         bubble.setInflateSynchronously(mInflateSynchronously);
         bubble.setShouldAutoExpand(true);
         bubble.markAsAccessedAt(System.currentTimeMillis());
@@ -1211,11 +1232,8 @@
             // Skip update, but store it in user bubbles so it gets restored after user switch
             mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
                     true /* shownInShade */);
-            if (DEBUG_BUBBLE_CONTROLLER) {
-                Log.d(TAG,
-                        "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
-                                + " current userId=" + mCurrentUserId);
-            }
+            Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId
+                    + " currentUser=" + mCurrentUserId);
         }
     }
 
@@ -1252,7 +1270,9 @@
         }
 
         String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
-        Log.i(TAG, "showOrHideAppBubble, with key: " + appBubbleKey);
+        Log.i(TAG, "showOrHideAppBubble, key= " + appBubbleKey + " stackVisibility= "
+                + (mStackView != null ? mStackView.getVisibility() : " null ")
+                + " statusBarShade=" + mIsStatusBarShade);
         PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
         if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
 
@@ -1386,6 +1406,13 @@
         removeFromWindowManagerMaybe();
         mLayerView = null;
         mStackView = null;
+
+        if (!mBubbleData.hasBubbles()) {
+            // if there are no bubbles, don't create the stack or layer views. they will be created
+            // later when the first bubble is added.
+            return;
+        }
+
         ensureBubbleViewsAndWindowCreated();
 
         // inflate bubble views
@@ -1714,8 +1741,11 @@
         @Override
         public void removeBubble(Bubble removedBubble) {
             if (mLayerView != null) {
-                // TODO: need to check if there's something that needs to happen here, e.g. if
-                //  the currently selected & expanded bubble is removed?
+                mLayerView.removeBubble(removedBubble);
+                if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+                    mLayerView.setVisibility(INVISIBLE);
+                    removeFromWindowManagerMaybe();
+                }
             }
         }
 
@@ -1770,18 +1800,19 @@
 
         @Override
         public void applyUpdate(BubbleData.Update update) {
-            if (DEBUG_BUBBLE_CONTROLLER) {
-                Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
-                        + " bubbleRemoved="
-                        + (update.removedBubbles != null && update.removedBubbles.size() > 0)
-                        + " bubbleUpdated=" + (update.updatedBubble != null)
-                        + " orderChanged=" + update.orderChanged
-                        + " expandedChanged=" + update.expandedChanged
-                        + " selectionChanged=" + update.selectionChanged
-                        + " suppressed=" + (update.suppressedBubble != null)
-                        + " unsuppressed=" + (update.unsuppressedBubble != null)
-                        + " shouldShowEducation=" + update.shouldShowEducation);
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+                    + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+                    + " expanded=%b selectionChanged=%b selected=%s"
+                    + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+                    update.addedBubble != null ? update.addedBubble.getKey() : "null",
+                    update.removedBubbles.isEmpty(),
+                    update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
+                    update.orderChanged, update.expandedChanged, update.expanded,
+                    update.selectionChanged,
+                    update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
+                    update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
+                    update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
+                    update.shouldShowEducation);
 
             ensureBubbleViewsAndWindowCreated();
 
@@ -1975,7 +2006,8 @@
         if (mStackView == null && mLayerView == null) {
             return;
         }
-
+        ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s",
+                mIsStatusBarShade, hasBubbles());
         if (!mIsStatusBarShade) {
             // Bubbles don't appear when the device is locked.
             if (mStackView != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 127c7e8..6d3f0c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -15,12 +15,10 @@
  */
 package com.android.wm.shell.bubbles;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.annotation.NonNull;
 import android.app.PendingIntent;
@@ -37,10 +35,12 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.common.bubbles.RemovedBubble;
 
@@ -333,9 +333,6 @@
     }
 
     public void setExpanded(boolean expanded) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "setExpanded: " + expanded);
-        }
         setExpandedInternal(expanded);
         dispatchPendingChanges();
     }
@@ -347,9 +344,8 @@
      * updated to have the correct state.
      */
     public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleFromLauncher=%s",
+                (bubble != null ? bubble.getKey() : "null"));
         mExpanded = true;
         if (Objects.equals(bubble, mSelectedBubble)) {
             return;
@@ -370,9 +366,6 @@
     }
 
     public void setSelectedBubble(BubbleViewProvider bubble) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "setSelectedBubble: " + bubble);
-        }
         setSelectedBubbleInternal(bubble);
         dispatchPendingChanges();
     }
@@ -427,15 +420,16 @@
     /**
      * When this method is called it is expected that all info in the bubble has completed loading.
      * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
-     * BubbleIconFactory, boolean)
+     * BubbleBarLayerView, BubbleIconFactory, boolean)
      */
     void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "notificationEntryUpdated: " + bubble);
-        }
         mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
         Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
         suppressFlyout |= !bubble.isTextChanged();
+        ProtoLog.d(WM_SHELL_BUBBLES,
+                "notifEntryUpdated=%s prevBubble=%b suppressFlyout=%b showInShade=%b autoExpand=%b",
+                bubble.getKey(), (prevBubble != null), suppressFlyout, showInShade,
+                bubble.shouldAutoExpand());
 
         if (prevBubble == null) {
             // Create a new bubble
@@ -483,9 +477,6 @@
      * Dismisses the bubble with the matching key, if it exists.
      */
     public void dismissBubbleWithKey(String key, @DismissReason int reason) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
-        }
         doRemove(key, reason);
         dispatchPendingChanges();
     }
@@ -603,9 +594,7 @@
     }
 
     private void doAdd(Bubble bubble) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "doAdd: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "doAdd=%s", bubble.getKey());
         mBubbles.add(0, bubble);
         mStateChange.addedBubble = bubble;
         // Adding the first bubble doesn't change the order
@@ -634,9 +623,7 @@
     }
 
     private void doUpdate(Bubble bubble, boolean reorder) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "doUpdate: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "BubbleData - doUpdate=%s", bubble.getKey());
         mStateChange.updatedBubble = bubble;
         if (!isExpanded() && reorder) {
             int prevPos = mBubbles.indexOf(bubble);
@@ -663,9 +650,6 @@
     }
 
     private void doRemove(String key, @DismissReason int reason) {
-        if (DEBUG_BUBBLE_DATA || (key != null && key.contains(KEY_APP_BUBBLE))) {
-            Log.d(TAG, "doRemove: " + key + " reason: " + reason);
-        }
         //  If it was pending remove it
         if (mPendingBubbles.containsKey(key)) {
             mPendingBubbles.remove(key);
@@ -686,9 +670,7 @@
                     && shouldRemoveHiddenBubble) {
 
                 Bubble b = getOverflowBubbleWithKey(key);
-                if (DEBUG_BUBBLE_DATA) {
-                    Log.d(TAG, "Cancel overflow bubble: " + b);
-                }
+                ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
                 if (b != null) {
                     b.stopInflation();
                 }
@@ -699,9 +681,7 @@
             }
             if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
                 Bubble b = getSuppressedBubbleWithKey(key);
-                if (DEBUG_BUBBLE_DATA) {
-                    Log.d(TAG, "Cancel suppressed bubble: " + b);
-                }
+                ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel suppressed bubble=%s", key);
                 if (b != null) {
                     mSuppressedBubbles.remove(b.getLocusId());
                     b.stopInflation();
@@ -711,6 +691,7 @@
             return;
         }
         Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+        ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey());
         bubbleToRemove.stopInflation();
         overflowBubble(reason, bubbleToRemove);
 
@@ -744,17 +725,12 @@
         }
         // Move selection to the new bubble at the same position.
         int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
-        }
         BubbleViewProvider newSelected = mBubbles.get(newIndex);
         setSelectedBubbleInternal(newSelected);
     }
 
     private void doSuppress(Bubble bubble) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "doSuppressed: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "doSuppress=%s", bubble.getKey());
         mStateChange.suppressedBubble = bubble;
         bubble.setSuppressBubble(true);
 
@@ -777,9 +753,7 @@
     }
 
     private void doUnsuppress(Bubble bubble) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "doUnsuppressed: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "doUnsuppress=%s", bubble.getKey());
         bubble.setSuppressBubble(false);
         mStateChange.unsuppressedBubble = bubble;
         mBubbles.add(bubble);
@@ -801,9 +775,7 @@
                 || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
             return;
         }
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "Overflowing: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
         mLogger.logOverflowAdd(bubble, reason);
         mOverflowBubbles.remove(bubble);
         mOverflowBubbles.add(0, bubble);
@@ -812,9 +784,7 @@
         if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
             // Remove oldest bubble.
             Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
-            if (DEBUG_BUBBLE_DATA) {
-                Log.d(TAG, "Overflow full. Remove: " + oldest);
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "overflow full, remove=%s", oldest.getKey());
             mStateChange.bubbleRemoved(oldest, Bubbles.DISMISS_OVERFLOW_MAX_REACHED);
             mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED);
             mOverflowBubbles.remove(oldest);
@@ -823,9 +793,7 @@
     }
 
     public void dismissAll(@DismissReason int reason) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "dismissAll: reason=" + reason);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "dismissAll reason=%d", reason);
         if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
             return;
         }
@@ -851,9 +819,10 @@
      * @param visible whether the task with the locusId is visible or not.
      */
     public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
-        }
+        if (locusId == null) return;
+
+        ProtoLog.d(WM_SHELL_BUBBLES, "onLocusVisibilityChanged=%s visible=%b taskId=%d",
+                locusId.getId(), visible, taskId);
 
         Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
         // Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
@@ -910,9 +879,8 @@
      * @param bubble the new selected bubble
      */
     private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleInternal=%s",
+                (bubble != null ? bubble.getKey() : "null"));
         if (Objects.equals(bubble, mSelectedBubble)) {
             return;
         }
@@ -969,12 +937,10 @@
      * @param shouldExpand the new requested state
      */
     private void setExpandedInternal(boolean shouldExpand) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
-        }
         if (mExpanded == shouldExpand) {
             return;
         }
+        ProtoLog.d(WM_SHELL_BUBBLES, "setExpandedInternal=%b", shouldExpand);
         if (shouldExpand) {
             if (mBubbles.isEmpty() && !mShowingOverflow) {
                 Log.e(TAG, "Attempt to expand stack when empty!");
@@ -1026,9 +992,6 @@
      * @return true if the position of any bubbles changed as a result
      */
     private boolean repackAll() {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "repackAll()");
-        }
         if (mBubbles.isEmpty()) {
             return false;
         }
@@ -1069,7 +1032,6 @@
     /**
      * The set of bubbles in row.
      */
-    @VisibleForTesting(visibility = PACKAGE)
     public List<Bubble> getBubbles() {
         return Collections.unmodifiableList(mBubbles);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index f56b171..f1a68e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -37,17 +37,7 @@
 
     // Default log tag for the Bubbles package.
     public static final String TAG_BUBBLES = "Bubbles";
-
-    static final boolean DEBUG_BUBBLE_CONTROLLER = false;
-    static final boolean DEBUG_BUBBLE_DATA = false;
-    static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
-    static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
-    static final boolean DEBUG_EXPERIMENTS = true;
-    static final boolean DEBUG_OVERFLOW = false;
     public static final boolean DEBUG_USER_EDUCATION = false;
-    static final boolean DEBUG_POSITIONER = false;
-    public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
-    public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
 
     private static final boolean FORCE_SHOW_USER_EDUCATION = false;
     private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index efc4d8b..088660e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -23,10 +23,10 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -67,6 +67,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.AlphaOptimizedButton;
 import com.android.wm.shell.common.TriangleShape;
@@ -199,13 +200,9 @@
 
         @Override
         public void onInitialized() {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
-                        + " initialized=" + mInitialized
-                        + " bubble=" + getBubbleKey());
-            }
-
             if (mDestroyed || mInitialized) {
+                ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+                        mDestroyed, mInitialized, getBubbleKey());
                 return;
             }
 
@@ -216,10 +213,8 @@
             // TODO: I notice inconsistencies in lifecycle
             // Post to keep the lifecycle normal
             post(() -> {
-                if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                    Log.d(TAG, "onInitialized: calling startActivity, bubble="
-                            + getBubbleKey());
-                }
+                ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+                        getBubbleKey());
                 try {
                     Rect launchBounds = new Rect();
                     mTaskView.getBoundsOnScreen(launchBounds);
@@ -279,10 +274,8 @@
 
         @Override
         public void onTaskCreated(int taskId, ComponentName name) {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onTaskCreated: taskId=" + taskId
-                        + " bubble=" + getBubbleKey());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+                    taskId, getBubbleKey());
             // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
             mTaskId = taskId;
 
@@ -298,15 +291,15 @@
 
         @Override
         public void onTaskVisibilityChanged(int taskId, boolean visible) {
+            ProtoLog.d(WM_SHELL_BUBBLES, "onTaskVisibilityChanged=%b bubble=%s taskId=%d",
+                    visible, getBubbleKey(), taskId);
             setContentVisibility(visible);
         }
 
         @Override
         public void onTaskRemovalStarted(int taskId) {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
-                        + " bubble=" + getBubbleKey());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+                    taskId, getBubbleKey());
             if (mBubble != null) {
                 mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
             }
@@ -644,9 +637,6 @@
         super.onDetachedFromWindow();
         mImeVisible = false;
         mNeedsNewHeight = false;
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
-        }
     }
 
     /**
@@ -805,10 +795,6 @@
      * and setting {@code false} actually means rendering the contents in transparent.
      */
     public void setContentVisibility(boolean visibility) {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "setContentVisibility: visibility=" + visibility
-                    + " bubble=" + getBubbleKey());
-        }
         mIsContentVisible = visibility;
         if (mTaskView != null && !mIsAnimating) {
             mTaskView.setAlpha(visibility ? 1f : 0f);
@@ -867,9 +853,6 @@
      * Sets the bubble used to populate this view.
      */
     void update(Bubble bubble) {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "update: bubble=" + bubble);
-        }
         if (mStackView == null) {
             Log.w(TAG, "Stack is null for bubble: " + bubble);
             return;
@@ -958,11 +941,6 @@
                 }
                 mNeedsNewHeight = false;
             }
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
-                        + " height=" + height
-                        + " mNeedsNewHeight=" + mNeedsNewHeight);
-            }
         }
     }
 
@@ -974,10 +952,6 @@
      *                                  waiting for layout.
      */
     public void updateView(int[] containerLocationOnScreen) {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "updateView: bubble="
-                    + getBubbleKey());
-        }
         mExpandedViewContainerLocation = containerLocationOnScreen;
         updateHeight();
         if (mTaskView != null
@@ -1103,9 +1077,6 @@
 
     /** Hide the task view. */
     public void cleanUpExpandedState() {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
-        }
         if (mTaskView != null) {
             mTaskView.setVisibility(GONE);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 9655470..70cdc82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -16,9 +16,9 @@
 
 package com.android.wm.shell.bubbles;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -28,7 +28,6 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -43,6 +42,7 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.wm.shell.R;
 
@@ -245,9 +245,6 @@
 
             Bubble toRemove = update.removedOverflowBubble;
             if (toRemove != null) {
-                if (DEBUG_OVERFLOW) {
-                    Log.d(TAG, "remove: " + toRemove);
-                }
                 toRemove.cleanupViews();
                 final int indexToRemove = mOverflowBubbles.indexOf(toRemove);
                 mOverflowBubbles.remove(toRemove);
@@ -257,9 +254,6 @@
             Bubble toAdd = update.addedOverflowBubble;
             if (toAdd != null) {
                 final int indexToAdd = mOverflowBubbles.indexOf(toAdd);
-                if (DEBUG_OVERFLOW) {
-                    Log.d(TAG, "add: " + toAdd + " prevIndex: " + indexToAdd);
-                }
                 if (indexToAdd > 0) {
                     mOverflowBubbles.remove(toAdd);
                     mOverflowBubbles.add(0, toAdd);
@@ -272,10 +266,9 @@
 
             updateEmptyStateVisibility();
 
-            if (DEBUG_OVERFLOW) {
-                Log.d(TAG, BubbleDebugConfig.formatBubblesString(
-                        mController.getOverflowBubbles(), null));
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "Apply overflow update, added=%s removed=%s",
+                    (toAdd != null ? toAdd.getKey() : "null"),
+                    (toRemove != null ? toRemove.getKey() : "null"));
         }
     };
 }
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 d62c86c..c03b6f8 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
@@ -16,18 +16,20 @@
 
 package com.android.wm.shell.bubbles;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.util.Log;
 import android.view.Surface;
 import android.view.WindowManager;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
 
@@ -36,9 +38,6 @@
  * placement and positioning calculations to refer to.
  */
 public class BubblePositioner {
-    private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
-            ? "BubblePositioner"
-            : BubbleDebugConfig.TAG_BUBBLES;
 
     /** The screen edge the bubble stack is pinned to */
     public enum StackPinnedEdge {
@@ -110,16 +109,12 @@
      */
     public void update(DeviceConfig deviceConfig) {
         mDeviceConfig = deviceConfig;
-
-        if (BubbleDebugConfig.DEBUG_POSITIONER) {
-            Log.w(TAG, "update positioner:"
-                    + " rotation: " + mRotation
-                    + " insets: " + deviceConfig.getInsets()
-                    + " isLargeScreen: " + deviceConfig.isLargeScreen()
-                    + " isSmallTablet: " + deviceConfig.isSmallTablet()
-                    + " showingInBubbleBar: " + mShowingInBubbleBar
-                    + " bounds: " + deviceConfig.getWindowBounds());
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "update positioner: "
+                        + "rotation=%d insets=%s largeScreen=%b "
+                        + "smallTablet=%b isBubbleBar=%b bounds=%s",
+                mRotation, deviceConfig.getInsets(), deviceConfig.isLargeScreen(),
+                deviceConfig.isSmallTablet(), mShowingInBubbleBar,
+                deviceConfig.getWindowBounds());
         updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 9facef3..c5bc9eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,7 +21,6 @@
 
 import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
@@ -1310,13 +1309,9 @@
         final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
         final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
                 && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
-        if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
-            Log.d(TAG, "Show manage edu: " + shouldShow);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow);
         if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
-            if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
-                Log.d(TAG, "Want to show manage edu, but it is forced hidden");
-            }
+            Log.w(TAG, "Want to show manage edu, but it is forced hidden");
             return false;
         }
         return shouldShow;
@@ -1360,13 +1355,9 @@
         }
         final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
         final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
-        if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
-            Log.d(TAG, "Show stack edu: " + shouldShow);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "Show stack edu=%b", shouldShow);
         if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
-            if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
-                Log.d(TAG, "Want to show stack edu, but it is forced hidden");
-            }
+            Log.w(TAG, "Want to show stack edu, but it is forced hidden");
             return false;
         }
         return shouldShow;
@@ -1513,7 +1504,7 @@
         mBubbleSize = mPositioner.getBubbleSize();
         for (Bubble b : mBubbleData.getBubbles()) {
             if (b.getIconView() == null) {
-                Log.d(TAG, "Display size changed. Icon null: " + b);
+                Log.w(TAG, "Display size changed. Icon null: " + b);
                 continue;
             }
             b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
@@ -1819,10 +1810,6 @@
     // via BubbleData.Listener
     @SuppressLint("ClickableViewAccessibility")
     void addBubble(Bubble bubble) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "addBubble: " + bubble);
-        }
-
         final boolean firstBubble = getBubbleCount() == 0;
 
         if (firstBubble && shouldShowStackEdu()) {
@@ -1868,9 +1855,6 @@
 
     // via BubbleData.Listener
     void removeBubble(Bubble bubble) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "removeBubble: " + bubble);
-        }
         if (isExpanded() && getBubbleCount() == 1) {
             mRemovingLastBubbleWhileExpanded = true;
             // We're expanded while the last bubble is being removed. Let the scrim animate away
@@ -1917,7 +1901,7 @@
             bubble.cleanupViews();
             logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
-            Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+            Log.w(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
         }
     }
 
@@ -1985,10 +1969,6 @@
      */
     // via BubbleData.Listener
     public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
-        }
-
         if (bubbleToSelect == null) {
             mBubbleData.setShowingOverflow(false);
             return;
@@ -2081,10 +2061,6 @@
      */
     // via BubbleData.Listener
     public void setExpanded(boolean shouldExpand) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "setExpanded: " + shouldExpand);
-        }
-
         if (!shouldExpand) {
             // If we're collapsing, release the animating-out surface immediately since we have no
             // need for it, and this ensures it cannot remain visible as we collapse.
@@ -2126,7 +2102,6 @@
      * Monitor for swipe up gesture that is used to collapse expanded view
      */
     void startMonitoringSwipeUpGesture() {
-        ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
 
         if (isGestureNavEnabled()) {
@@ -2174,7 +2149,6 @@
      * Stop monitoring for swipe up gesture
      */
     void stopMonitoringSwipeUpGesture() {
-        ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
     }
 
@@ -2202,9 +2176,6 @@
     }
 
     void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
-        }
         if (suppressed) {
             int index = getBubbleIndex(bubble);
             mBubbleContainer.removeViewAt(index);
@@ -2339,6 +2310,8 @@
     }
 
     private void animateExpansion() {
+        ProtoLog.d(WM_SHELL_BUBBLES, "animateExpansion, expandedBubble=%s",
+                mExpandedBubble != null ? mExpandedBubble.getKey() : "null");
         cancelDelayedExpandCollapseSwitchAnimations();
         final boolean showVertically = mPositioner.showBubblesVertically();
         mIsExpanded = true;
@@ -2465,7 +2438,7 @@
 
     private void animateCollapse() {
         cancelDelayedExpandCollapseSwitchAnimations();
-
+        ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse");
         if (isManageEduVisible()) {
             mManageEduView.hide();
         }
@@ -2508,11 +2481,6 @@
                 mManageEduView.hide();
             }
 
-            if (DEBUG_BUBBLE_STACK_VIEW) {
-                Log.d(TAG, "animateCollapse");
-                Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
-                        mExpandedBubble));
-            }
             updateZOrder();
             updateBadges(true /* setBadgeForCollapsedStack */);
             afterExpandedViewAnimation();
@@ -2612,6 +2580,18 @@
                         mExpandedViewTemporarilyHidden = false;
                         mIsBubbleSwitchAnimating = false;
                         mExpandedViewContainer.setAnimationMatrix(null);
+
+                        // When a bubble is being dragged, the expanded view is temporarily hidden.
+                        // If the motion ends with dismissing the bubble, with multiple bubbles in
+                        // the stack, we'll end up here to switch to the new bubble. However, the
+                        // expanded view animation might not actually set the z ordering for the
+                        // expanded view correctly, because the view may still be temporarily
+                        // hidden. So set it again here.
+                        BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+                        if (bev != null) {
+                            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+                            mExpandedBubble.getExpandedView().setAnimating(false);
+                        }
                     })
                     .start();
         }, 25);
@@ -2691,7 +2671,7 @@
                 if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
                     mExpandedViewContainer.animate().translationY(newExpandedViewTop);
                 }
-                List<Animator> animList = new ArrayList();
+                List<Animator> animList = new ArrayList<>();
                 for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
                     View child = mBubbleContainer.getChildAt(i);
                     float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
@@ -2894,7 +2874,7 @@
         if (!shouldShowFlyout(bubble)) {
             return;
         }
-
+        ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
         mFlyoutDragDeltaX = 0f;
         clearFlyoutOnHide();
         mAfterFlyoutHidden = () -> {
@@ -3036,6 +3016,9 @@
     @VisibleForTesting
     public void showManageMenu(boolean show) {
         if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
+        ProtoLog.d(WM_SHELL_BUBBLES, "showManageMenu=%b for bubble=%s",
+                show, (mExpandedBubble != null ? mExpandedBubble.getKey() : "null"));
+
         mShowingManage = show;
 
         // This should not happen, since the manage menu is only visible when there's an expanded
@@ -3167,10 +3150,6 @@
     }
 
     private void updateExpandedBubble() {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "updateExpandedBubble()");
-        }
-
         mExpandedViewContainer.removeAllViews();
         if (mIsExpanded && mExpandedBubble != null
                 && mExpandedBubble.getExpandedView() != null) {
@@ -3318,9 +3297,6 @@
     }
 
     private void updateExpandedView() {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
-        }
         boolean isOverflowExpanded = mExpandedBubble != null
                 && BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
         int[] paddings = mPositioner.getExpandedViewContainerPadding(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5fc67d7..530ec5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -20,7 +20,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -31,9 +31,11 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.taskview.TaskView;
 
 /**
@@ -78,11 +80,8 @@
 
         @Override
         public void onInitialized() {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
-                        + " initialized=" + mInitialized
-                        + " bubble=" + getBubbleKey());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+                    mDestroyed, mInitialized, getBubbleKey());
 
             if (mDestroyed || mInitialized) {
                 return;
@@ -98,10 +97,8 @@
             // TODO: I notice inconsistencies in lifecycle
             // Post to keep the lifecycle normal
             mParentView.post(() -> {
-                if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                    Log.d(TAG, "onInitialized: calling startActivity, bubble="
-                            + getBubbleKey());
-                }
+                ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+                        getBubbleKey());
                 try {
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
@@ -158,10 +155,8 @@
 
         @Override
         public void onTaskCreated(int taskId, ComponentName name) {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onTaskCreated: taskId=" + taskId
-                        + " bubble=" + getBubbleKey());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+                    taskId, getBubbleKey());
             // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
             mTaskId = taskId;
 
@@ -177,15 +172,14 @@
 
         @Override
         public void onTaskRemovalStarted(int taskId) {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
-                        + " bubble=" + getBubbleKey());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+                    taskId, getBubbleKey());
             if (mBubble != null) {
                 mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
             }
             if (mTaskView != null) {
                 mTaskView.release();
+                ((ViewGroup) mParentView).removeView(mTaskView);
                 mTaskView = null;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index 845dca3..e43609f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -15,14 +15,11 @@
  */
 package com.android.wm.shell.bubbles.animation;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubbleExpandedView.BACKGROUND_ALPHA;
 import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
 import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
 import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -31,7 +28,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.content.Context;
-import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.ViewConfiguration;
 
@@ -41,6 +37,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BubbleExpandedView;
@@ -55,8 +52,6 @@
  */
 public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
 
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
-
     private static final float COLLAPSE_THRESHOLD = 0.02f;
 
     private static final int COLLAPSE_DURATION_MS = 250;
@@ -121,9 +116,6 @@
     @Override
     public void setExpandedView(BubbleExpandedView expandedView) {
         if (mExpandedView != null) {
-            if (DEBUG_COLLAPSE_ANIMATOR) {
-                Log.d(TAG, "updating expandedView, resetting previous");
-            }
             if (mCollapseAnimation != null) {
                 mCollapseAnimation.cancel();
             }
@@ -140,17 +132,14 @@
         if (mExpandedView != null) {
             mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
 
-            if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
-                Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES,
+                    "updateDrag: distance=%f dragged=%d", distance, mDraggedAmount);
 
             setCollapsedAmount(mDraggedAmount);
 
             if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
                 mNotifiedAboutThreshold = true;
-                if (DEBUG_COLLAPSE_ANIMATOR) {
-                    Log.d(TAG, "notifying over collapse threshold");
-                }
+                ProtoLog.d(WM_SHELL_BUBBLES, "notifying over collapse threshold");
                 vibrateIfEnabled();
             }
         }
@@ -172,45 +161,35 @@
         if (mSwipeDownVelocity > mMinFlingVelocity) {
             // Swipe velocity is positive and over fling velocity.
             // This is a swipe down, always reset to expanded state, regardless of dragged amount.
-            if (DEBUG_COLLAPSE_ANIMATOR) {
-                Log.d(TAG,
-                        "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
-                                + " minV: " + mMinFlingVelocity);
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES,
+                    "not collapsing expanded view, swipe down velocity=%f minV=%d",
+                    mSwipeDownVelocity, mMinFlingVelocity);
             return false;
         }
 
         if (mSwipeUpVelocity > mMinFlingVelocity) {
             // Swiping up and over fling velocity, collapse the view.
-            if (DEBUG_COLLAPSE_ANIMATOR) {
-                Log.d(TAG,
-                        "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
-                                + mMinFlingVelocity);
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES,
+                    "collapse expanded view, swipe up velocity=%f minV=%d",
+                    mSwipeUpVelocity, mMinFlingVelocity);
             return true;
         }
 
         if (isPastCollapseThreshold()) {
-            if (DEBUG_COLLAPSE_ANIMATOR) {
-                Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES,
+                    "collapse expanded view, past threshold, dragged=%d", mDraggedAmount);
             return true;
         }
 
-        if (DEBUG_COLLAPSE_ANIMATOR) {
-            Log.d(TAG, "not collapsing expanded view");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "not collapsing expanded view");
 
         return false;
     }
 
     @Override
     public void animateCollapse(Runnable startStackCollapse, Runnable after) {
-        if (DEBUG_COLLAPSE_ANIMATOR) {
-            Log.d(TAG,
-                    "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
-                            + mMinFlingVelocity);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
+                mSwipeUpVelocity,  mMinFlingVelocity);
         if (mExpandedView != null) {
             // Mark it as animating immediately to avoid updates to the view before animation starts
             mExpandedView.setAnimating(true);
@@ -243,9 +222,7 @@
 
     @Override
     public void animateBackToExpanded() {
-        if (DEBUG_COLLAPSE_ANIMATOR) {
-            Log.d(TAG, "expandedView animate back to expanded");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate back to expanded");
         BubbleExpandedView expandedView = mExpandedView;
         if (expandedView == null) {
             return;
@@ -298,9 +275,7 @@
 
     @Override
     public void reset() {
-        if (DEBUG_COLLAPSE_ANIMATOR) {
-            Log.d(TAG, "reset expandedView collapsed state");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
         if (mExpandedView == null) {
             return;
         }
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 00d683e..73a9cf4 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
@@ -266,13 +266,8 @@
         mListener.onBackPressed();
     }
 
-    /** Cleans up task view, should be called when the bubble is no longer active. */
+    /** Cleans up the expanded view, should be called when the bubble is no longer active. */
     public void cleanUpExpandedState() {
-        if (mBubbleTaskViewHelper != null) {
-            if (mTaskView != null) {
-                removeView(mTaskView);
-            }
-        }
         mMenuViewController.hideMenu(false /* animated */);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bd8ce80..62f2726 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -34,7 +34,9 @@
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -61,6 +63,7 @@
     private static final float SCRIM_ALPHA = 0.2f;
 
     private final BubbleController mBubbleController;
+    private final BubbleData mBubbleData;
     private final BubblePositioner mPositioner;
     private final BubbleBarAnimationHelper mAnimationHelper;
     private final BubbleEducationViewController mEducationViewController;
@@ -85,9 +88,10 @@
     private TouchDelegate mHandleTouchDelegate;
     private final Rect mHandleTouchBounds = new Rect();
 
-    public BubbleBarLayerView(Context context, BubbleController controller) {
+    public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
         super(context);
         mBubbleController = controller;
+        mBubbleData = bubbleData;
         mPositioner = mBubbleController.getPositioner();
 
         mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -236,15 +240,47 @@
         showScrim(true);
     }
 
+    /** Removes the given {@code bubble}. */
+    public void removeBubble(Bubble bubble) {
+        if (mBubbleData.getBubbles().isEmpty()) {
+            // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+            // at the end.
+            collapse(bubble::cleanupViews);
+        } else {
+            bubble.cleanupViews();
+        }
+    }
+
     /** Collapses any showing expanded view */
     public void collapse() {
+        collapse(/* endAction= */ null);
+    }
+
+    /**
+     * Collapses any showing expanded view.
+     *
+     * @param endAction an action to run and the end of the collapse animation.
+     */
+    public void collapse(@Nullable Runnable endAction) {
+        if (!mIsExpanded) {
+            return;
+        }
         mIsExpanded = false;
         final BubbleBarExpandedView viewToRemove = mExpandedView;
         mEducationViewController.hideEducation(/* animated = */ true);
+        Runnable runnable = () -> {
+            removeView(viewToRemove);
+            if (endAction != null) {
+                endAction.run();
+            }
+            if (mBubbleData.getBubbles().isEmpty()) {
+                mBubbleController.onAllBubblesAnimatedOut();
+            }
+        };
         if (mDragController != null && mDragController.isStuckToDismiss()) {
-            mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+            mAnimationHelper.animateDismiss(runnable);
         } else {
-            mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+            mAnimationHelper.animateCollapse(runnable);
         }
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index e1dea3b..33b61b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -17,18 +17,19 @@
 package com.android.wm.shell.bubbles.properties
 
 import android.os.SystemProperties
+import com.android.wm.shell.Flags
 
 /** Provides bubble properties in production. */
 object ProdBubbleProperties : BubbleProperties {
 
-    // TODO(b/256873975) Should use proper flag when available to shell/launcher
-    private var _isBubbleBarEnabled =
+    private var _isBubbleBarEnabled = Flags.enableBubbleBar() ||
             SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
 
     override val isBubbleBarEnabled
         get() = _isBubbleBarEnabled
 
     override fun refresh() {
-        _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+        _isBubbleBarEnabled = Flags.enableBubbleBar() ||
+                SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
     }
 }
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 b52a118..d4ed017 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
@@ -83,7 +83,6 @@
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Optional<DragAndDropController> dragAndDropController,
             Transitions transitions,
             TransactionPool transactionPool,
             IconProvider iconProvider,
@@ -94,8 +93,8 @@
             SystemWindows systemWindows) {
         return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
-                displayImeController, displayInsetsController, dragAndDropController, transitions,
-                transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
-                mainHandler, systemWindows);
+                displayImeController, displayInsetsController, transitions, transactionPool,
+                iconProvider, recentTasks, launchAdjacentController, 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 fc97c798..0d6a852 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
@@ -78,7 +78,6 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -203,20 +202,6 @@
 
     @WMSingleton
     @Provides
-    static Optional<DragAndDropController> provideDragAndDropController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellCommandHandler shellCommandHandler,
-            DisplayController displayController,
-            UiEventLogger uiEventLogger,
-            IconProvider iconProvider,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
-                shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(
             Context context,
             ShellInit shellInit,
@@ -911,7 +896,6 @@
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Optional<DragAndDropController> dragAndDropControllerOptional,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
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 36f06e8..ead5ad2 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
@@ -172,7 +172,7 @@
             BubblePositioner positioner,
             DisplayController displayController,
             @DynamicOverride Optional<OneHandedController> oneHandedOptional,
-            Optional<DragAndDropController> dragAndDropController,
+            DragAndDropController dragAndDropController,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
@@ -338,7 +338,7 @@
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Optional<DragAndDropController> dragAndDropController,
+            DragAndDropController dragAndDropController,
             Transitions transitions,
             TransactionPool transactionPool,
             IconProvider iconProvider,
@@ -553,6 +553,24 @@
     }
 
     //
+    // Drag and drop
+    //
+
+    @WMSingleton
+    @Provides
+    static DragAndDropController provideDragAndDropController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
+            DisplayController displayController,
+            UiEventLogger uiEventLogger,
+            IconProvider iconProvider,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new DragAndDropController(context, shellInit, shellController,
+                shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor);
+    }
+
+    //
     // Misc
     //
 
@@ -562,6 +580,7 @@
     @ShellCreateTriggerOverride
     @Provides
     static Object provideIndependentShellComponentsToCreate(
+            DragAndDropController dragAndDropController,
             DefaultMixedHandler defaultMixedHandler) {
         return new Object();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 0000000..fd91ac0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+    ShellCommandHandler.ShellCommandActionHandler {
+
+    override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+        return when (args[0]) {
+            "moveToDesktop" -> {
+                if (!runMoveToDesktop(args, pw)) {
+                    pw.println("Task not found. Please enter a valid taskId.")
+                    false
+                } else {
+                    true
+                }
+            }
+
+            else -> {
+                pw.println("Invalid command: ${args[0]}")
+                false
+            }
+        }
+    }
+
+    private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+        if (args.size < 2) {
+            // First argument is the action name.
+            pw.println("Error: task id should be provided as arguments")
+            return false
+        }
+
+        val taskId = try {
+            args[1].toInt()
+        } catch (e: NumberFormatException) {
+            pw.println("Error: task id should be an integer")
+            return false
+        }
+
+        return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction())
+    }
+
+    override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+        pw.println("$prefix moveToDesktop <taskId> ")
+        pw.println("$prefix  Move a task with given id to desktop mode.")
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index f82212d..7c8fcbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -248,6 +248,12 @@
 
         // Check if count changed
         if (prevCount != newCount) {
+            KtProtoLog.d(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
+                prevCount,
+                newCount
+            )
             notifyVisibleTaskListeners(displayId, newCount)
         }
     }
@@ -262,6 +268,11 @@
      * Get number of tasks that are marked as visible on given [displayId]
      */
     fun getVisibleTaskCount(displayId: Int): Int {
+        KtProtoLog.d(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTaskRepo: visibleTaskCount= %d",
+            displayData[displayId]?.visibleTasks?.size ?: 0
+        )
         return displayData[displayId]?.visibleTasks?.size ?: 0
     }
 
@@ -290,6 +301,10 @@
             taskId
         )
         freeformTasksInZOrder.remove(taskId)
+        KtProtoLog.d(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
+        )
     }
 
     /**
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 a089e81..e872849 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
@@ -100,6 +100,9 @@
 
     private val desktopMode: DesktopModeImpl
     private var visualIndicator: DesktopModeVisualIndicator? = null
+    private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+        DesktopModeShellCommandHandler(this)
+
     private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
         t: SurfaceControl.Transaction ->
         visualIndicator?.releaseVisualIndicator(t)
@@ -148,6 +151,8 @@
     private fun onInit() {
         KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
         shellCommandHandler.addDumpCallback(this::dump, this)
+        shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+            this)
         shellController.addExternalInterface(
             ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
             { createExternalInterface() },
@@ -240,6 +245,40 @@
         }
     }
 
+    /** Move a task with given `taskId` to desktop without decor */
+    fun moveToDesktopWithoutDecor(
+        taskId: Int,
+        wct: WindowContainerTransaction
+    ): Boolean {
+        val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
+        moveToDesktopWithoutDecor(task, wct)
+        return true
+    }
+
+    /**
+     * Move a task to desktop without decor
+     */
+    private fun moveToDesktopWithoutDecor(
+        task: RunningTaskInfo,
+        wct: WindowContainerTransaction
+    ) {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d",
+            task.taskId
+        )
+        exitSplitIfApplicable(wct, task)
+        // Bring other apps to front first
+        bringDesktopAppsToFront(task.displayId, wct)
+        addMoveToDesktopChanges(wct, task)
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
     /**
      * Move a task to desktop
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348..3fad28a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@
 Todo
 - Per-feature docs
 - Feature flagging
-- Best practices
\ No newline at end of file
+- Best practices & patterns
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326e..9aa5f4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@
 Launcher uses.
 
 If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
 `wm_shell_util-sources` filegroup.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d96..7070dea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@
 (especially as products override components).
 
 The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
   (provides threading-related components)
-  - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+  - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
     (provides components that are likely common to all products, ie. DisplayController,
     Transactions, etc.)
-    - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+    - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
       (phone/tablet specific components only)
-    - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+    - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
       (PIP specific components for TV)
-      - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+      - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
         (TV specific components only)
   - etc.
 
@@ -43,7 +43,7 @@
 product it runs on.  If there are hooks that can be added to the component, that is the
 preferable approach.
 
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
 annotation to allow the product module to provide an implementation that the base module can
 reference.  This is most useful if the existence of the entire component is controlled by the
 product and the override implementation is optional (there is a default implementation).  More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4..438aa76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@
 ### Kotlin
 
 Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
 class which has a similar API to the Java ProtoLog class.
 
 ### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
 ```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
 ```
 
 ## Winscope Tracing
 
 The Winscope tool is extremely useful in determining what is happening on-screen in both
 WindowManager and SurfaceFlinger.  Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool.  This trace will contain all the information about the windows/activities/surfaces on
+screen.
 
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+## WindowManager/SurfaceFlinger hierarchy dump
 
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
 ```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
+
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates.  In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha  # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
+
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
 ```
 
 ## Dumps
@@ -69,6 +117,21 @@
 - Update `WMShell` if you are dumping SysUI state
 - Inject `ShellCommandHandler` into your Shell class, and add a dump callback
 
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
 ## Debugging in Android Studio
 
 If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6a..b489fe8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@
 
 ## Where does the code live
 
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
 directory and is included as a part dependency of the host SystemUI apk.
 
 ## How do I build the Shell library
 
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
 ```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
 ```
 But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
 file.
 
 Normally, you would build it as a part of the host SystemUI, for example via commandline:
 ```shell
 # Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
 ```
 
 Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 0000000..0e20a8e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+    // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e6..30ff669 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@
 # Run a specific command
 adb shell dumpsys activity service SystemUIService WMShell help
 adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-```
\ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333..98af930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
 ## Unit tests
 
 New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
 be run via command line using `atest`:
 ```shell
 atest WMShellUnitTests
@@ -25,10 +25,24 @@
 and SurfaceFlinger traces captured during the run.
 
 New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
 ```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
 ```
 
 **Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac74889..9d01535 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@
 
 ## Dagger setup
 
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
 for example, the Executors and Handlers for the various threads that are used.  You can request
 an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
 `@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@
   want to dedupe multiple messages
   - In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
     assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
   - It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
 
 ### Testing
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 fdfb6f3..269c369 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
@@ -105,25 +105,7 @@
         void onDragStarted();
     }
 
-    /**
-     * Creates {@link DragAndDropController}. Returns {@code null} if the feature is disabled.
-     */
-    public static DragAndDropController create(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellCommandHandler shellCommandHandler,
-            DisplayController displayController,
-            UiEventLogger uiEventLogger,
-            IconProvider iconProvider,
-            ShellExecutor mainExecutor) {
-        if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
-            return null;
-        }
-        return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
-                displayController, uiEventLogger, iconProvider, mainExecutor);
-    }
-
-    DragAndDropController(Context context,
+    public DragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
             ShellCommandHandler shellCommandHandler,
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 52a06e0..9f73f1b 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
@@ -1891,6 +1891,9 @@
     }
 
     private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+        ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "removeContentOverlay: %s, state=%s, surface=%s",
+                mTaskInfo, mPipTransitionState, surface);
         if (mPipOverlay != null) {
             if (mPipOverlay != surface) {
                 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
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 238e6b5..c5a0102 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
@@ -797,14 +797,20 @@
                     mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
                     mPipBoundsState.getStashedState());
 
-            updateDisplayLayout.run();
+            // Scale PiP on density dpi change, so it appears to be the same size physically.
+            final boolean densityDpiChanged =
+                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+                    && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+                            != layout.densityDpi());
+            if (densityDpiChanged) {
+                final float scale = (float) layout.densityDpi()
+                        / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
+                postChangeBounds.set(0, 0,
+                        (int) (postChangeBounds.width() * scale),
+                        (int) (postChangeBounds.height() * scale));
+            }
 
-            // Resize the PiP bounds to be at the same scale relative to the new size spec. For
-            // example, if PiP was resized to 90% of the maximum size on the previous layout,
-            // make sure it is 90% of the new maximum size spec.
-            postChangeBounds.set(0, 0,
-                    (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
-                    (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+            updateDisplayLayout.run();
 
             // Calculate the PiP bounds in the new orientation based on same fraction along the
             // rotated movement bounds.
@@ -821,10 +827,6 @@
             mPipBoundsState.setHasUserResizedPip(true);
             mTouchHandler.setUserResizeBounds(postChangeBounds);
 
-            final boolean densityDpiChanged =
-                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
-                            && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
-                            != layout.densityDpi());
             if (densityDpiChanged) {
                 // Using PipMotionHelper#movePip directly here may cause race condition since
                 // the app content in PiP mode may or may not be updated for the new density dpi.
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 70cb2fc..1b124c2 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
@@ -184,7 +184,7 @@
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
-    private final Optional<DragAndDropController> mDragAndDropController;
+    private final DragAndDropController mDragAndDropController;
     private final Transitions mTransitions;
     private final TransactionPool mTransactionPool;
     private final IconProvider mIconProvider;
@@ -214,7 +214,7 @@
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Optional<DragAndDropController> dragAndDropController,
+            DragAndDropController dragAndDropController,
             Transitions transitions,
             TransactionPool transactionPool,
             IconProvider iconProvider,
@@ -290,7 +290,7 @@
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
-        mDragAndDropController = Optional.of(dragAndDropController);
+        mDragAndDropController = dragAndDropController;
         mTransitions = transitions;
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
@@ -328,7 +328,9 @@
             // TODO: Multi-display
             mStageCoordinator = createStageCoordinator();
         }
-        mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+        if (mDragAndDropController != null) {
+            mDragAndDropController.setSplitScreenController(this);
+        }
         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
         mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index e8894a83..b60e361 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -451,6 +451,8 @@
             mPendingResize.onConsumed(aborted);
             mPendingResize = null;
         }
+
+        // TODO: handle transition consumed for active remote handler
     }
 
     void onFinish(WindowContainerTransaction wct) {
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 c101425..aec4d11 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
@@ -74,7 +74,6 @@
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Optional<DragAndDropController> dragAndDropController,
             Transitions transitions,
             TransactionPool transactionPool,
             IconProvider iconProvider,
@@ -85,7 +84,7 @@
             SystemWindows systemWindows) {
         super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
-                displayInsetsController, dragAndDropController, transitions, transactionPool,
+                displayInsetsController, null, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
                 Optional.empty(), mainExecutor);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index e86b62d..6325c68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,7 @@
                                 SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
                                 TransactionPool transactionPool, Rect firstWindowFrame,
                                 int mainWindowShiftLength, float roundedCornerRadius) {
-            mFromYDelta = fromYDelta - roundedCornerRadius;
+            mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
             mToYDelta = toYDelta;
             mOccludeHoleView = occludeHoleView;
             mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4355ed2..94519a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -74,6 +74,9 @@
             @Override
             public void onTransitionFinished(WindowContainerTransaction wct,
                     SurfaceControl.Transaction sct) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                        "Finished one-shot remote transition %s for (#%d).", mRemote,
+                        info.getDebugId());
                 if (mRemote.asBinder() != null) {
                     mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
@@ -82,8 +85,8 @@
                 }
                 mMainExecutor.execute(() -> {
                     finishCallback.onTransitionFinished(wct);
+                    mRemote = null;
                 });
-                mRemote = null;
             }
         };
         Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -115,17 +118,24 @@
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
+                + " transition %s for (#%d).", mRemote, info.getDebugId());
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
             public void onTransitionFinished(WindowContainerTransaction wct,
                     SurfaceControl.Transaction sct) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                        "Finished merging one-shot remote transition %s for (#%d).", mRemote,
+                        info.getDebugId());
                 // We have merged, since we sent the transaction over binder, the one in this
                 // process won't be cleared if the remote applied it. We don't actually know if the
                 // remote applied the transaction, but applying twice will break surfaceflinger
                 // so just assume the worst-case and clear the local transaction.
                 t.clear();
-                mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
-                mRemote = null;
+                mMainExecutor.execute(() -> {
+                    finishCallback.onTransitionFinished(wct);
+                    mRemote = null;
+                });
             }
         };
         try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
new file mode 100644
index 0000000..2ff4d90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
@@ -0,0 +1,4 @@
+# WM shell transition tracing owners
+# Bug component: 1157642
+natanieljr@google.com
+pablogamito@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 99df6a3..fa331af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -18,16 +18,16 @@
 
 import android.internal.perfetto.protos.PerfettoTrace;
 import android.os.SystemClock;
-import android.tracing.perfetto.DataSourceInstance;
+import android.os.Trace;
 import android.tracing.perfetto.DataSourceParams;
 import android.tracing.perfetto.InitArguments;
 import android.tracing.perfetto.Producer;
-import android.tracing.perfetto.TracingContext;
 import android.tracing.transition.TransitionDataSource;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -40,6 +40,7 @@
             mActiveTraces::incrementAndGet,
             this::onFlush,
             mActiveTraces::decrementAndGet);
+    private final Map<String, Integer> mHandlerMapping = new HashMap<>();
 
     public PerfettoTransitionTracer() {
         Producer.init(InitArguments.DEFAULTS);
@@ -58,8 +59,17 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+        try {
+            doLogDispatched(transitionId, handler);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
         mDataSource.trace(ctx -> {
-            final int handlerId = getHandlerId(handler, ctx);
+            final int handlerId = getHandlerId(handler);
 
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -71,17 +81,16 @@
         });
     }
 
-    private static int getHandlerId(Transitions.TransitionHandler handler,
-            TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) {
-        final Map<String, Integer> handlerMapping =
-                ctx.getCustomTlsState().handlerMapping;
+    private int getHandlerId(Transitions.TransitionHandler handler) {
         final int handlerId;
-        if (handlerMapping.containsKey(handler.getClass().getName())) {
-            handlerId = handlerMapping.get(handler.getClass().getName());
-        } else {
-            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
-            handlerId = handlerMapping.size() + 1;
-            handlerMapping.put(handler.getClass().getName(), handlerId);
+        synchronized (mHandlerMapping) {
+            if (mHandlerMapping.containsKey(handler.getClass().getName())) {
+                handlerId = mHandlerMapping.get(handler.getClass().getName());
+            } else {
+                // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+                handlerId = mHandlerMapping.size() + 1;
+                mHandlerMapping.put(handler.getClass().getName(), handlerId);
+            }
         }
         return handlerId;
     }
@@ -97,6 +106,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+        try {
+            doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -120,10 +138,19 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+        try {
+            doLogMerged(mergedTransitionId, playingTransitionId);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
-            os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+            os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
             os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
                     SystemClock.elapsedRealtimeNanos());
             os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
@@ -142,6 +169,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+        try {
+            doLogAborted(transitionId);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogAborted(int transitionId) {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -160,13 +196,18 @@
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
-            final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
-            for (String handler : handlerMapping.keySet()) {
-                final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
-                os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+            final long mappingsToken = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+            for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
+                final String handler = entry.getKey();
+                final int handlerId = entry.getValue();
+
+                final long mappingEntryToken = os.start(PerfettoTrace.ShellHandlerMappings.MAPPING);
+                os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
                 os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
-                os.end(token);
+                os.end(mappingEntryToken);
+
             }
+            os.end(mappingsToken);
 
             ctx.flush();
         });
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 a8b39c41..891eea0 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
@@ -888,7 +888,10 @@
     private DesktopModeWindowDecoration getFocusedDecor() {
         final int size = mWindowDecorByTaskId.size();
         DesktopModeWindowDecoration focusedDecor = null;
-        for (int i = 0; i < size; i++) {
+        // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+        //  a decor for a closed task. This is a short term fix while the core issue is addressed,
+        //  which involves refactoring the window decor lifecycle to be visibility based.
+        for (int i = size - 1; i >= 0; i--) {
             final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
             if (decor != null && decor.isFocused()) {
                 focusedDecor = decor;
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 e272958..4c2eff3 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
@@ -137,6 +137,16 @@
 
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
+    /** Checks [standardAppHelper] 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) }
+    }
+
     @Postsubmit
     @Test
     override fun focusChanges() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 75965d6..1668e37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -143,7 +143,7 @@
                 bubbleController,
                 mainExecutor
             )
-        bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+        bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
     }
 
     @Test
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 6ebfc63..ac75c07 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,9 +41,9 @@
 #endif  // __ANDROID__
 }
 
-inline bool inter_character_justification() {
+inline bool letter_spacing_justification() {
 #ifdef __ANDROID__
-    return com_android_text_flags_inter_character_justification();
+    return com_android_text_flags_letter_spacing_justification();
 #else
     return true;
 #endif  // __ANDROID__
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 72ddecc..3d7e559 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -1,6 +1,13 @@
 package: "com.android.graphics.hwui.flags"
 
 flag {
+  name: "clip_shader"
+  namespace: "core_graphics"
+  description: "API for canvas shader clipping operations"
+  bug: "280116960"
+}
+
+flag {
   name: "matrix_44"
   namespace: "core_graphics"
   description: "API for 4x4 matrix and related canvas functions"
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5613369..d66d7f8 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,7 +72,7 @@
     const minikin::Range contextRange(contextStart, contextStart + contextCount);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+    const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
                                                     ? paint->getRunFlag()
                                                     : minikin::RunFlag::NONE;
 
@@ -105,7 +105,7 @@
     const minikin::Range range(start, start + count);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+    const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
                                                     ? paint->getRunFlag()
                                                     : minikin::RunFlag::NONE;
 
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 295f4dc..e5bdeee 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -763,41 +763,41 @@
 }; // namespace CanvasJNI
 
 static const JNINativeMethod gMethods[] = {
-    {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
-    {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
-    {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
-    {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion},
+        {"nGetNativeFinalizer", "()J", (void*)CanvasJNI::getNativeFinalizer},
+        {"nFreeCaches", "()V", (void*)CanvasJNI::freeCaches},
+        {"nFreeTextLayoutCaches", "()V", (void*)CanvasJNI::freeTextLayoutCaches},
+        {"nSetCompatibilityVersion", "(I)V", (void*)CanvasJNI::setCompatibilityVersion},
 
-    // ------------ @FastNative ----------------
-    {"nInitRaster", "(J)J", (void*) CanvasJNI::initRaster},
-    {"nSetBitmap", "(JJ)V", (void*) CanvasJNI::setBitmap},
-    {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+        // ------------ @FastNative ----------------
+        {"nInitRaster", "(J)J", (void*)CanvasJNI::initRaster},
+        {"nSetBitmap", "(JJ)V", (void*)CanvasJNI::setBitmap},
+        {"nGetClipBounds", "(JLandroid/graphics/Rect;)Z", (void*)CanvasJNI::getClipBounds},
 
-    // ------------ @CriticalNative ----------------
-    {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
-    {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
-    {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
-    {"nSave","(JI)I", (void*) CanvasJNI::save},
-    {"nSaveLayer","(JFFFFJ)I", (void*) CanvasJNI::saveLayer},
-    {"nSaveLayerAlpha","(JFFFFI)I", (void*) CanvasJNI::saveLayerAlpha},
-    {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer},
-    {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer},
-    {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
-    {"nRestore","(J)Z", (void*) CanvasJNI::restore},
-    {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
-    {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
-    {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
-    {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
-    {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
-    {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
-    {"nScale","(JFF)V", (void*) CanvasJNI::scale},
-    {"nSkew","(JFF)V", (void*) CanvasJNI::skew},
-    {"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
-    {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
-    {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
-    {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
-    {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
-    {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
+        // ------------ @CriticalNative ----------------
+        {"nIsOpaque", "(J)Z", (void*)CanvasJNI::isOpaque},
+        {"nGetWidth", "(J)I", (void*)CanvasJNI::getWidth},
+        {"nGetHeight", "(J)I", (void*)CanvasJNI::getHeight},
+        {"nSave", "(JI)I", (void*)CanvasJNI::save},
+        {"nSaveLayer", "(JFFFFJ)I", (void*)CanvasJNI::saveLayer},
+        {"nSaveLayerAlpha", "(JFFFFI)I", (void*)CanvasJNI::saveLayerAlpha},
+        {"nSaveUnclippedLayer", "(JIIII)I", (void*)CanvasJNI::saveUnclippedLayer},
+        {"nRestoreUnclippedLayer", "(JIJ)V", (void*)CanvasJNI::restoreUnclippedLayer},
+        {"nGetSaveCount", "(J)I", (void*)CanvasJNI::getSaveCount},
+        {"nRestore", "(J)Z", (void*)CanvasJNI::restore},
+        {"nRestoreToCount", "(JI)V", (void*)CanvasJNI::restoreToCount},
+        {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
+        {"nSetMatrix", "(JJ)V", (void*)CanvasJNI::setMatrix},
+        {"nConcat", "(JJ)V", (void*)CanvasJNI::concat},
+        {"nConcat", "(J[F)V", (void*)CanvasJNI::concat44},
+        {"nRotate", "(JF)V", (void*)CanvasJNI::rotate},
+        {"nScale", "(JFF)V", (void*)CanvasJNI::scale},
+        {"nSkew", "(JFF)V", (void*)CanvasJNI::skew},
+        {"nTranslate", "(JFF)V", (void*)CanvasJNI::translate},
+        {"nQuickReject", "(JJ)Z", (void*)CanvasJNI::quickRejectPath},
+        {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+        {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect},
+        {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath},
+        {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter},
 };
 
 // If called from Canvas these are regular JNI
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index d55d28d..b5f7caa 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,8 @@
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
 
+#include <sstream>
+
 #include "Properties.h"
 #include "RenderThread.h"
 #include "pipeline/skia/ShaderCache.h"
@@ -40,7 +42,8 @@
 namespace uirenderer {
 namespace renderthread {
 
-static std::array<std::string_view, 20> sEnableExtensions{
+// Not all of these are strictly required, but are all enabled if present.
+static std::array<std::string_view, 21> sEnableExtensions{
         VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
         VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
         VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +64,7 @@
         VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
         VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
         VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+        VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
 };
 
 static bool shouldEnableExtension(const std::string_view& extension) {
@@ -303,6 +307,15 @@
     *tailPNext = ycbcrFeature;
     tailPNext = &ycbcrFeature->pNext;
 
+    if (grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+        VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures =
+                new VkPhysicalDeviceFaultFeaturesEXT;
+        deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+        deviceFaultFeatures->pNext = nullptr;
+        *tailPNext = deviceFaultFeatures;
+        tailPNext = &deviceFaultFeatures->pNext;
+    }
+
     // query to get the physical device features
     mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
     // this looks like it would slow things down,
@@ -405,6 +418,79 @@
     });
 }
 
+namespace {
+void onVkDeviceFault(const std::string& contextLabel, const std::string& description,
+                     const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                     const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                     const std::vector<std::byte>& vendorBinaryData) {
+    // The final crash string should contain as much differentiating info as possible, up to 1024
+    // bytes. As this final message is constructed, the same information is also dumped to the logs
+    // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+    // statement is always placed first to give context.
+    ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", contextLabel.c_str(), description.c_str());
+    std::stringstream crashMsg;
+    crashMsg << "VK_ERROR_DEVICE_LOST (" << contextLabel;
+
+    if (!addressInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+        crashMsg << ", " << addressInfos.size() << " address info (";
+        for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+            ALOGE(" addressType:       %d", (int)addressInfo.addressType);
+            ALOGE("  reportedAddress:  %" PRIu64, addressInfo.reportedAddress);
+            ALOGE("  addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+            crashMsg << addressInfo.addressType << ":"
+                     << addressInfo.reportedAddress << ":"
+                     << addressInfo.addressPrecision << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur);  // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+        crashMsg << ", " << vendorInfos.size() << " vendor info (";
+        for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+            ALOGE(" description:      %s", vendorInfo.description);
+            ALOGE("  vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+            ALOGE("  vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+            // Omit descriptions for individual vendor info structs in the crash string, as the
+            // fault code and fault data fields should be enough for clustering, and the verbosity
+            // isn't worth it. Additionally, vendors may just set the general description field of
+            // the overall fault to the description of the first element in this list, and that
+            // overall description will be placed at the end of the crash string.
+            crashMsg << vendorInfo.vendorFaultCode << ":"
+                     << vendorInfo.vendorFaultData << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur);  // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorBinaryData.empty()) {
+        // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+        ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+              " Stack team if you observe this message).",
+              vendorBinaryData.size());
+        crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+    }
+
+    crashMsg << "): " << description;
+    LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+}
+
+void deviceLostProcRenderThread(void* callbackContext, const std::string& description,
+                                const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                                const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                                const std::vector<std::byte>& vendorBinaryData) {
+    onVkDeviceFault("RenderThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+void deviceLostProcUploadThread(void* callbackContext, const std::string& description,
+                                const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                                const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                                const std::vector<std::byte>& vendorBinaryData) {
+    onVkDeviceFault("UploadThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+}  // anonymous namespace
+
 static void onGrContextReleased(void* context) {
     VulkanManager* manager = (VulkanManager*)context;
     manager->decStrong((void*)onGrContextReleased);
@@ -430,6 +516,10 @@
     backendContext.fVkExtensions = &mExtensions;
     backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
     backendContext.fGetProc = std::move(getProc);
+    backendContext.fDeviceLostContext = nullptr;
+    backendContext.fDeviceLostProc = (contextType == ContextType::kRenderThread)
+                                             ? deviceLostProcRenderThread
+                                             : deviceLostProcUploadThread;
 
     LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
     this->incStrong((void*)onGrContextReleased);
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 09f40e8..970bfda 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -195,7 +195,7 @@
      * <li>SBAS: 120-151, 183-192</li>
      * <li>GLONASS: One of: OSN, or FCN+100
      * <ul>
-     * <li>1-24 as the orbital slot number (OSN) (preferred, if known)</li>
+     * <li>1-25 as the orbital slot number (OSN) (preferred, if known)</li>
      * <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
      * i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
      * </ul></li>
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 461dafb..ec1edb8 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -219,6 +219,27 @@
      * called on the main thread as data will not be loaded from raw assets. Returns true if a Mean
      * Sea Level altitude is added to the {@code location}; otherwise, returns false and leaves the
      * {@code location} unchanged.
+     *
+     * <p>Prior calls to {@link #addMslAltitudeToLocation(Context, Location)} off the main thread
+     * are necessary to load data from raw assets. Example code on the main thread is as follows:
+     *
+     * <pre>{@code
+     *   if (!mAltitudeConverter.addMslAltitudeToLocation(location)) {
+     *       // Queue up only one call off the main thread.
+     *       if (mIsAltitudeConverterIdle) {
+     *           mIsAltitudeConverterIdle = false;
+     *           executeOffMainThread(() -> {
+     *               try {
+     *                   // Load raw assets for next call attempt on main thread.
+     *                   mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+     *               } catch (IOException e) {
+     *                   Log.e(TAG, "Not loading raw assets: " + e);
+     *               }
+     *               mIsAltitudeConverterIdle = true;
+     *           });
+     *       }
+     *   }
+     * }</pre>
      */
     @FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL)
     public boolean addMslAltitudeToLocation(@NonNull Location location) {
diff --git a/location/java/com/android/internal/location/altitude/GeoidMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java
index 9bf5689..df9ca97 100644
--- a/location/java/com/android/internal/location/altitude/GeoidMap.java
+++ b/location/java/com/android/internal/location/altitude/GeoidMap.java
@@ -51,6 +51,10 @@
  */
 public final class GeoidMap {
 
+    private static final String GEOID_HEIGHT_PREFIX = "geoid-height";
+
+    private static final String EXPIRATION_DISTANCE_PREFIX = "expiration-distance";
+
     private static final Object GEOID_HEIGHT_PARAMS_LOCK = new Object();
 
     private static final Object EXPIRATION_DISTANCE_PARAMS_LOCK = new Object();
@@ -82,8 +86,7 @@
     public static MapParamsProto getGeoidHeightParams(@NonNull Context context) throws IOException {
         synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
             if (sGeoidHeightParams == null) {
-                // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
-                sGeoidHeightParams = parseParams(context);
+                sGeoidHeightParams = parseParams(context, GEOID_HEIGHT_PREFIX);
             }
             return sGeoidHeightParams;
         }
@@ -99,17 +102,17 @@
             throws IOException {
         synchronized (EXPIRATION_DISTANCE_PARAMS_LOCK) {
             if (sExpirationDistanceParams == null) {
-                // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
-                sExpirationDistanceParams = parseParams(context);
+                sExpirationDistanceParams = parseParams(context, EXPIRATION_DISTANCE_PREFIX);
             }
             return sExpirationDistanceParams;
         }
     }
 
     @NonNull
-    private static MapParamsProto parseParams(@NonNull Context context) throws IOException {
+    private static MapParamsProto parseParams(@NonNull Context context, @NonNull String prefix)
+            throws IOException {
         try (InputStream is = context.getApplicationContext().getAssets().open(
-                "geoid_height_map/map-params.pb")) {
+                "geoid_map/" + prefix + "-params.pb")) {
             return MapParamsProto.parseFrom(is.readAllBytes());
         }
     }
@@ -267,7 +270,8 @@
     @NonNull
     public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
             @NonNull long[] s2CellIds) throws IOException {
-        return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles);
+        return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles,
+                GEOID_HEIGHT_PREFIX);
     }
 
     /**
@@ -278,7 +282,8 @@
     @NonNull
     public double[] readExpirationDistances(@NonNull MapParamsProto params,
             @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
-        return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles);
+        return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles,
+                EXPIRATION_DISTANCE_PREFIX);
     }
 
     /**
@@ -288,15 +293,16 @@
      */
     @NonNull
     private static double[] readMapValues(@NonNull MapParamsProto params, @NonNull Context context,
-            @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles)
-            throws IOException {
+            @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles,
+            @NonNull String prefix) throws IOException {
         validate(params, s2CellIds);
         double[] mapValuesMeters = new double[s2CellIds.length];
         if (getMapValues(params, cacheTiles::get, s2CellIds, mapValuesMeters)) {
             return mapValuesMeters;
         }
 
-        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles);
+        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles,
+                prefix);
         if (getMapValues(params, loadedTiles, s2CellIds, mapValuesMeters)) {
             return mapValuesMeters;
         }
@@ -338,7 +344,8 @@
     @NonNull
     private static TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
             @NonNull Context context, @NonNull long[] s2CellIds,
-            @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
+            @NonNull LruCache<Long, S2TileProto> cacheTiles, @NonNull String prefix)
+            throws IOException {
         int len = s2CellIds.length;
 
         // Enable batch loading by finding all cache keys upfront.
@@ -374,7 +381,7 @@
 
             S2TileProto tile;
             try (InputStream is = context.getApplicationContext().getAssets().open(
-                    "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+                    "geoid_map/" + prefix + "-disk-tile-" + diskTokens[i] + ".pb")) {
                 tile = S2TileProto.parseFrom(is.readAllBytes());
             }
             mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles, cacheTiles);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 69708ec..e9ba779 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5166,13 +5166,12 @@
      *     dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
      *     the request was successful but the dispatch of focus change was delayed due to a fade
      *     operation.
-     * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
-     *     other active {@link AudioFocusInfo} are {@code null}.
      * @hide
      */
     @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
     @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @FocusRequestResult
     public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
             @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
             @Nullable FadeManagerConfiguration transientFadeMgrConfig) {
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index e4dc152..0613fc6 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -15,6 +15,9 @@
  */
 package android.media;
 
+import static android.media.audio.Flags.FLAG_SCO_MANAGED_BY_AUDIO;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.bluetooth.BluetoothProfile;
@@ -174,4 +177,13 @@
     public boolean isLeOutput() {
         return mIsLeOutput;
     }
+
+    /**
+     * Factory method for <code>BluetoothProfileConnectionInfo</code> for an HFP device.
+     */
+    @FlaggedApi(FLAG_SCO_MANAGED_BY_AUDIO)
+    public static @NonNull BluetoothProfileConnectionInfo createHfpInfo() {
+        return new BluetoothProfileConnectionInfo(BluetoothProfile.HEADSET, false,
+                -1, false);
+    }
 }
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index e8adfaf..48ca4bf 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -50,6 +50,14 @@
 
     private static final String TAG = "AudioProductStrategy";
 
+    /**
+     * The audio flags that will affect product strategy selection.
+     */
+    private static final int AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION =
+            AudioAttributes.FLAG_AUDIBILITY_ENFORCED
+                    | AudioAttributes.FLAG_SCO
+                    | AudioAttributes.FLAG_BEACON;
+
     private final AudioAttributesGroup[] mAudioAttributesGroups;
     private final String mName;
     /**
@@ -438,8 +446,8 @@
                 || (attr.getSystemUsage() == refAttr.getSystemUsage()))
             && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
                 || (attr.getContentType() == refAttr.getContentType()))
-            && ((refAttr.getAllFlags() == 0)
-                || (attr.getAllFlags() != 0
+            && (((refAttr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) == 0)
+                || ((attr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) != 0
                 && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
             && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
     }
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 694756c..eb980a3 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -16,9 +16,11 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -51,14 +53,14 @@
     /**
      * Request option: one-way
      * <p> With this option, no response is expected after sending the request.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int REQUEST_OPTION_ONEWAY = 2;
     /**
      * Request option: one-shot
      * <p> With this option, only one response will be given per request.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int REQUEST_OPTION_ONESHOT = 3;
 
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 84c197d..be93abb 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -111,7 +111,7 @@
     void resumeRecording(in IBinder sessionToken, in Bundle params, int userId);
 
     // For playback control
-    void startPlayback(in IBinder sessionToken, int userId);
+    void resumePlayback(in IBinder sessionToken, int userId);
     void stopPlayback(in IBinder sessionToken, int mode, int userId);
 
     // For broadcast info
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 7b20c39..6b247cc 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -63,7 +63,7 @@
     void timeShiftSetMode(int mode);
     void timeShiftEnablePositionTracking(boolean enable);
 
-    void startPlayback();
+    void resumePlayback();
     void stopPlayback(int mode);
 
     // For the recording session
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 999e2cf..d145397 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -80,7 +80,7 @@
     private static final int DO_SET_TV_MESSAGE_ENABLED = 31;
     private static final int DO_NOTIFY_TV_MESSAGE = 32;
     private static final int DO_STOP_PLAYBACK = 33;
-    private static final int DO_START_PLAYBACK = 34;
+    private static final int DO_RESUME_PLAYBACK = 34;
     private static final int DO_SET_VIDEO_FROZEN = 35;
     private static final int DO_NOTIFY_AD_SESSION_DATA = 36;
 
@@ -295,8 +295,8 @@
                 mTvInputSessionImpl.stopPlayback(msg.arg1);
                 break;
             }
-            case DO_START_PLAYBACK: {
-                mTvInputSessionImpl.startPlayback();
+            case DO_RESUME_PLAYBACK: {
+                mTvInputSessionImpl.resumePlayback();
                 break;
             }
             case DO_SET_VIDEO_FROZEN: {
@@ -523,8 +523,8 @@
     }
 
     @Override
-    public void startPlayback() {
-        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_PLAYBACK));
+    public void resumePlayback() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RESUME_PLAYBACK));
     }
 
 
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 8720bfe..aed3e60e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -35,6 +36,7 @@
 import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.ad.TvAdManager;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.TvInteractiveAppManager;
 import android.net.Uri;
 import android.os.Binder;
@@ -131,7 +133,8 @@
             VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION, VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING,
             VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD, VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE,
             VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID, VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT,
-            VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN})
+            VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN,
+            VIDEO_UNAVAILABLE_REASON_STOPPED})
     public @interface VideoUnavailableReason {}
 
     /** Indicates that this TV message contains watermarking data */
@@ -344,9 +347,9 @@
     /**
      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
-     * it has been stopped by stopPlayback.
-     * @hide
+     * it has been stopped by {@link TvView#stopPlayback(int)}.
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19;
 
     /** @hide */
@@ -553,85 +556,85 @@
     /**
      * Informs the application that the session has been tuned to the given channel.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_CHANNEL_URI
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TUNED = "tuned";
 
     /**
      * Sends the type and ID of a selected track. This is used to inform the application that a
      * specific track is selected.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_TRACK_TYPE
      * @see SESSION_DATA_KEY_TRACK_ID
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
 
     /**
      * Sends the list of all audio/video/subtitle tracks.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_TRACKS
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
 
     /**
      * Informs the application that the video is now available for watching.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
 
     /**
      * Informs the application that the video became unavailable for some reason.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
 
     /**
      * Notifies response for broadcast info.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
             "broadcast_info_response";
 
     /**
      * Notifies response for advertisement.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_RESPONSE
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
 
     /**
      * Notifies the advertisement buffer is consumed.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_BUFFER
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
 
     /**
      * Sends the TV message.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see TvInputService.Session#notifyTvMessage(int, Bundle)
      * @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
 
 
@@ -654,9 +657,9 @@
      *
      * <p> Type: android.net.Uri
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
 
     /**
@@ -668,9 +671,9 @@
      * <p> Type: Integer
      *
      * @see TvTrackInfo#getType()
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
 
     /**
@@ -679,9 +682,9 @@
      * <p> Type: String
      *
      * @see TvTrackInfo#getId()
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
 
     /**
@@ -689,9 +692,9 @@
      *
      * <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TRACKS = "tracks";
 
     /**
@@ -701,9 +704,9 @@
      *
      * <p> Type: Integer
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
             "video_unavailable_reason";
 
@@ -712,9 +715,9 @@
      *
      * <p> Type: android.media.tv.BroadcastInfoResponse
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
 
     /**
@@ -722,9 +725,9 @@
      *
      * <p> Type: android.media.tv.AdResponse
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
 
     /**
@@ -732,9 +735,9 @@
      *
      * <p> Type: android.media.tv.AdBuffer
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
 
     /**
@@ -744,9 +747,9 @@
      *
      * <p> Type: Integer
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
 
 
@@ -3616,13 +3619,13 @@
             }
         }
 
-        void startPlayback() {
+        void resumePlayback() {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
             }
             try {
-                mService.startPlayback(mToken, mUserId);
+                mService.resumePlayback(mToken, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index a022b1c..6658918 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.MainThread;
@@ -35,6 +36,7 @@
 import android.media.AudioPresentation;
 import android.media.PlaybackParams;
 import android.media.tv.ad.TvAdManager;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.TvInteractiveAppService;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -766,12 +768,12 @@
         /**
          * Informs the application that the video freeze state has been updated.
          *
-         * When {@code true}, the video is frozen on the last frame but audio playback remains
+         * <p>When {@code true}, the video is frozen on the last frame but audio playback remains
          * active.
          *
          * @param isFrozen Whether or not the video is frozen
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void notifyVideoFreezeUpdated(boolean isFrozen) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -1260,7 +1262,7 @@
         }
 
         /**
-         * Notifies data related to this session to corresponding linked
+         * Sends data related to this session to corresponding linked
          * {@link android.media.tv.ad.TvAdService} object via TvAdView.
          *
          * <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
@@ -1270,21 +1272,21 @@
          *
          * @param type data type
          * @param data the related data values
-         * @hide
          */
-        public void notifyTvInputSessionData(
+        @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+        public void sendTvInputSessionData(
                 @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
                 public void run() {
                     try {
-                        if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+                        if (DEBUG) Log.d(TAG, "sendTvInputSessionData");
                         if (mSessionCallback != null) {
                             mSessionCallback.onTvInputSessionData(type, data);
                         }
                     } catch (RemoteException e) {
-                        Log.w(TAG, "error in notifyTvInputSessionData", e);
+                        Log.w(TAG, "error in sendTvInputSessionData", e);
                     }
                 }
             });
@@ -1439,10 +1441,10 @@
          *
          * @param type the type of the data
          * @param data a bundle contains the data received
-         * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+         * @see android.media.tv.ad.TvAdService.Session#sendTvAdSessionData(String, Bundle)
          * @see android.media.tv.ad.TvAdView#setTvView(TvView)
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
         public void onTvAdSessionData(
                 @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
         }
@@ -1615,20 +1617,20 @@
          * <p> Note that this is different form {@link #timeShiftPause()} as should release the
          * stream, making it impossible to resume from this position again.
          * @param mode
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void onStopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) {
         }
 
         /**
-         * Starts playback of the Audio, Video, and CC streams.
+         * Resumes playback of the Audio, Video, and CC streams.
          *
          * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be
-         * used after stopping playback. This is used to restart playback from the current position
+         * used after stopping playback. This is used to resume playback from the current position
          * in the live broadcast.
-         * @hide
          */
-        public void onStartPlayback() {
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onResumePlayback() {
         }
 
         /**
@@ -2112,10 +2114,10 @@
         }
 
         /**
-         * Calls {@link #onStartPlayback()}.
+         * Calls {@link #onResumePlayback()}.
          */
-        void startPlayback() {
-            onStartPlayback();
+        void resumePlayback() {
+            onResumePlayback();
         }
 
         /**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index cb45661..e604cb7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -37,6 +38,7 @@
 import android.media.tv.TvInputManager.Session;
 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
 import android.media.tv.TvInputManager.SessionCallback;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.TvInteractiveAppService;
 import android.net.Uri;
 import android.os.Bundle;
@@ -650,10 +652,10 @@
      * <p>The metadata that will continue to be filtered includes the PSI
      * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1.
      *
-     * <p> Note that this is different form {@link #timeShiftPause()} as this completely drops
+     * <p> Note that this is different from {@link #timeShiftPause()} as this completely drops
      * the stream, making it impossible to resume from this position again.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void stopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) {
         if (mSession != null) {
             mSession.stopPlayback(mode);
@@ -661,16 +663,19 @@
     }
 
     /**
-     * Starts playback of the Audio, Video, and CC streams.
+     * Resumes playback of the Audio, Video, and CC streams.
      *
-     * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be
-     * used after stopping playback. This is used to restart playback from the current position
-     * in the live broadcast.
-     * @hide
+     * <p> Note that this is different from {@link #timeShiftResume()} as this is intended to
+     * be used after {@link #stopPlayback(int)} has been called. This is used to resume
+     * playback from the current position in the live broadcast.
+
+     * <p> If this is the first time playback should begin, you will need to use
+     * {@link #tune(String, Uri, Bundle)} to begin playback.
      */
-    public void startPlayback() {
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+    public void resumePlayback() {
         if (mSession != null) {
-            mSession.startPlayback();
+            mSession.resumePlayback();
         }
     }
 
@@ -678,13 +683,15 @@
      * Sets whether or not the video is frozen. While the video is frozen, audio playback will
      * continue.
      *
-     * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+     * <p>This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
      * received with the command to freeze the video.
      *
-     * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+     * <p>This will freeze the video to the last frame when the state is set to {@code true}.
+     *
+     * @see TvView.TvInputCallback#setVideoFrozen(boolean)
      * @param isFrozen whether or not the video is frozen.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void setVideoFrozen(boolean isFrozen) {
         if (mSession != null) {
             mSession.setVideoFrozen(isFrozen);
@@ -1320,6 +1327,16 @@
         public void onTvMessage(@NonNull String inputId,
                 @TvInputManager.TvMessageType int type, @NonNull Bundle data) {
         }
+
+        /**
+         * This is called when the video freeze status is updated.
+         *
+         * @see #setVideoFrozen(boolean)
+         * @param inputId The ID of the TV input bound to this view.
+         * @param isFrozen Whether or not the video is currently frozen on the las
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onVideoFreezeUpdated(@NonNull String inputId, boolean isFrozen) {}
     }
 
     /**
@@ -1748,5 +1765,19 @@
                 mCallback.onTvMessage(mInputId, type, data);
             }
         }
+
+        @Override
+        public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+            if (DEBUG) {
+                Log.d(TAG, "onVideoFreezeUpdated(isFrozen=" + isFrozen + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onVideoFreezeUpdated - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onVideoFreezeUpdated(mInputId, isFrozen);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 02c0d75..59b10c6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -68,7 +68,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
 
@@ -77,7 +76,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
 
@@ -86,7 +84,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
 
@@ -95,7 +92,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
 
@@ -104,7 +100,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_BACK_URI = "back_uri";
 
@@ -112,7 +107,6 @@
      * Broadcast intent action to send app command to TV app.
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String ACTION_APP_LINK_COMMAND =
             "android.media.tv.ad.action.APP_LINK_COMMAND";
@@ -123,7 +117,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
 
@@ -134,7 +127,6 @@
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
      * @see TvAdServiceInfo#getId()
-     * @hide
      */
     public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
 
@@ -144,7 +136,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
 
@@ -155,7 +146,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
 
@@ -171,36 +161,32 @@
     /**
      * Sends an advertisement request to be processed by the related TV input.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_REQUEST
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
 
     /**
      * Notifies the advertisement buffer is ready.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_BUFFER
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
 
     /**
      * Sends request for broadcast info.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
 
     /**
      * Removes request for broadcast info.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
             "remove_broadcast_info_request";
@@ -220,8 +206,7 @@
      *
      * <p> Type: android.media.tv.AdRequest
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
 
@@ -230,8 +215,7 @@
      *
      * <p> Type: android.media.tv.AdBuffer
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
 
@@ -240,8 +224,7 @@
      *
      * <p> Type: android.media.tv.BroadcastInfoRequest
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
 
@@ -250,8 +233,7 @@
      *
      * <p> Type: Integer
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
 
@@ -437,11 +419,10 @@
     }
 
     /**
-     * Returns the complete list of TV AD service on the system.
+     * Returns the complete list of TV AD services on the system.
      *
      * @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
      * information.
-     * @hide
      */
     @NonNull
     public List<TvAdServiceInfo> getTvAdServiceList() {
@@ -495,8 +476,17 @@
      *
      * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
      *                  in {@link TvAdServiceInfo#getId()}.
-     * @param command The command to be sent.
-     * @hide
+     * @param command The command to be sent. The command is a bundle with the following keys:
+     *                <ul>
+     *                <li>{@link #APP_LINK_KEY_PACKAGE_NAME}: The package name of the app to be
+     *                launched.
+     *                <li>{@link #APP_LINK_KEY_CLASS_NAME}: The class name of the app to be
+     *                launched.
+     *                <li>{@link #APP_LINK_KEY_COMMAND_TYPE}: The command type.
+     *                <li>{@link #APP_LINK_KEY_SERVICE_ID}: The ID of the TV AD service.
+     *                <li>{@link #APP_LINK_KEY_BACK_URI}: The URI to be used to return to the
+     *                previous app.
+     *                </ul>
      */
     public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
         try {
@@ -511,7 +501,6 @@
      *
      * @param callback A callback used to monitor status of the TV AD services.
      * @param executor A {@link Executor} that the status change will be delivered to.
-     * @hide
      */
     public void registerCallback(
             @CallbackExecutor @NonNull Executor executor,
@@ -527,7 +516,6 @@
      * Unregisters the existing {@link TvAdServiceCallback}.
      *
      * @param callback The existing callback to remove.
-     * @hide
      */
     public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
         Preconditions.checkNotNull(callback);
@@ -1174,8 +1162,7 @@
     }
 
     /**
-     * Callback used to monitor status of the TV AD service.
-     * @hide
+     * Callback used to monitor status of the TV advertisement service.
      */
     public abstract static class TvAdServiceCallback {
         /**
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 4d8f5c8b..6c8a8fd 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -17,6 +17,7 @@
 package android.media.tv.ad;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -32,6 +33,7 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
 import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -61,8 +63,8 @@
 
 /**
  * The TvAdService class represents a TV client-side advertisement service.
- * @hide
  */
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
 public abstract class TvAdService extends Service {
     private static final boolean DEBUG = false;
     private static final String TAG = "TvAdService";
@@ -73,7 +75,6 @@
      * Name under which a TvAdService component publishes information about itself. This meta-data
      * must reference an XML resource containing an
      * <code>&lt;{@link android.R.styleable#TvAdService tv-ad-service}&gt;</code> tag.
-     * @hide
      */
     public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
 
@@ -92,7 +93,7 @@
 
     @Override
     @Nullable
-    public final IBinder onBind(@NonNull Intent intent) {
+    public final IBinder onBind(@Nullable Intent intent) {
         ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
             @Override
             public void registerCallback(ITvAdServiceCallback cb) {
@@ -135,7 +136,6 @@
      * Called when app link command is received.
      *
      * @see TvAdManager#sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public void onAppLinkCommand(@NonNull Bundle command) {
     }
@@ -198,7 +198,6 @@
          *
          * @param enable {@code true} if you want to enable the media view. {@code false}
          *            otherwise.
-         * @hide
          */
         @CallSuper
         public void setMediaViewEnabled(final boolean enable) {
@@ -224,7 +223,6 @@
          * Returns {@code true} if media view is enabled, {@code false} otherwise.
          *
          * @see #setMediaViewEnabled(boolean)
-         * @hide
          */
         public boolean isMediaViewEnabled() {
             return mMediaViewEnabled;
@@ -252,21 +250,18 @@
 
         /**
          * Starts TvAdService session.
-         * @hide
          */
         public void onStartAdService() {
         }
 
         /**
          * Stops TvAdService session.
-         * @hide
          */
         public void onStopAdService() {
         }
 
         /**
          * Resets TvAdService session.
-         * @hide
          */
         public void onResetAdService() {
         }
@@ -398,6 +393,7 @@
          * @param data the original bytes to be signed.
          *
          * @see #onSigningResult(String, byte[])
+         * @hide
          */
         @CallSuper
         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@@ -421,22 +417,22 @@
         }
 
         @Override
-        public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+        public boolean onKeyDown(int keyCode, @Nullable KeyEvent event) {
             return false;
         }
 
         @Override
-        public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+        public boolean onKeyLongPress(int keyCode, @Nullable KeyEvent event) {
             return false;
         }
 
         @Override
-        public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+        public boolean onKeyMultiple(int keyCode, int count, @Nullable KeyEvent event) {
             return false;
         }
 
         @Override
-        public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+        public boolean onKeyUp(int keyCode, @Nullable KeyEvent event) {
             return false;
         }
 
@@ -484,6 +480,7 @@
          * @param top Top position in pixels, relative to the overlay view.
          * @param right Right position in pixels, relative to the overlay view.
          * @param bottom Bottom position in pixels, relative to the overlay view.
+         *
          */
         @CallSuper
         public void layoutSurface(final int left, final int top, final int right,
@@ -615,9 +612,8 @@
          *
          * @param type the type of the data
          * @param data a bundle contains the data received
-         * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+         * @see android.media.tv.TvInputService.Session#sendTvInputSessionData(String, Bundle)
          * @see android.media.tv.ad.TvAdView#setTvView(TvView)
-         * @hide
          */
         public void onTvInputSessionData(
                 @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
@@ -633,7 +629,6 @@
          *
          * @param width The width of the media view, in pixels.
          * @param height The height of the media view, in pixels.
-         * @hide
          */
         public void onMediaViewSizeChanged(@Px int width, @Px int height) {
         }
@@ -643,7 +638,6 @@
          * implementation can override this method and return its own view.
          *
          * @return a view attached to the media window. {@code null} if no media view is created.
-         * @hide
          */
         @Nullable
         public View onCreateMediaView() {
@@ -651,27 +645,26 @@
         }
 
         /**
-         * Notifies data related to this session to corresponding linked
+         * Sends data related to this session to corresponding linked
          * {@link android.media.tv.TvInputService} object via TvView.
          *
          * @param type data type
          * @param data the related data values
          * @see TvAdView#setTvView(TvView)
-         * @hide
          */
-        public void notifyTvAdSessionData(
+        public void sendTvAdSessionData(
                 @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
                 public void run() {
                     try {
-                        if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+                        if (DEBUG) Log.d(TAG, "sendTvAdSessionData");
                         if (mSessionCallback != null) {
                             mSessionCallback.onTvAdSessionData(type, data);
                         }
                     } catch (RemoteException e) {
-                        Log.w(TAG, "error in notifyTvAdSessionData", e);
+                        Log.w(TAG, "error in sendTvAdSessionData", e);
                     }
                 }
             });
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index 45dc89d..bac14e7 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.ad;
 
+import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -26,6 +27,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -42,8 +44,8 @@
 
 /**
  * This class is used to specify meta information of a TV AD service.
- * @hide
  */
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
 public final class TvAdServiceInfo implements Parcelable {
     private static final boolean DEBUG = false;
     private static final String TAG = "TvAdServiceInfo";
@@ -95,6 +97,7 @@
         in.readStringList(mTypes);
     }
 
+    @NonNull
     public static final Creator<TvAdServiceInfo> CREATOR = new Creator<TvAdServiceInfo>() {
         @Override
         public TvAdServiceInfo createFromParcel(Parcel in) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 604dbd5..ee01468 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -17,6 +17,7 @@
 package android.media.tv.ad;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -29,6 +30,7 @@
 import android.media.tv.TvTrackInfo;
 import android.media.tv.TvView;
 import android.media.tv.ad.TvAdManager.Session.FinishedInputEventCallback;
+import android.media.tv.flags.Flags;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -48,9 +50,9 @@
 import java.util.concurrent.Executor;
 
 /**
- * Displays contents of TV AD services.
- * @hide
+ * Displays contents of TV advertisement services.
  */
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
 public class TvAdView extends ViewGroup {
     private static final String TAG = "TvAdView";
     private static final boolean DEBUG = false;
@@ -158,7 +160,6 @@
      * @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
      *               to unlink the TvView.
      * @return {@code true} if it's linked successfully; {@code false} otherwise.
-     * @hide
      */
     public boolean setTvView(@Nullable TvView tvView) {
         if (tvView == null) {
@@ -182,14 +183,12 @@
         return true;
     }
 
-    /** @hide */
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
         createSessionMediaView();
     }
 
-    /** @hide */
     @Override
     public void onDetachedFromWindow() {
         removeSessionMediaView();
@@ -259,7 +258,6 @@
      * Resets this TvAdView to release its resources.
      *
      * <p>It can be reused by call {@link #prepareAdService(String, String)}.
-     * @hide
      */
     public void reset() {
         if (DEBUG) Log.d(TAG, "reset()");
@@ -362,7 +360,6 @@
      *
      * @param event The input event.
      * @return {@code true} if the event was handled by the view, {@code false} otherwise.
-     * @hide
      */
     public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
         if (mOnUnhandledInputEventListener != null) {
@@ -391,7 +388,6 @@
      * by the TV AD service.
      *
      * @param listener The callback to be invoked when the unhandled input event is received.
-     * @hide
      */
     public void setOnUnhandledInputEventListener(
             @NonNull @CallbackExecutor Executor executor,
@@ -406,7 +402,6 @@
      *
      * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
      * @see #clearOnUnhandledInputEventListener()
-     * @hide
      */
     @Nullable
     public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
@@ -415,14 +410,13 @@
 
     /**
      * Clears the {@link OnUnhandledInputEventListener}.
-     * @hide
      */
     public void clearOnUnhandledInputEventListener() {
         mOnUnhandledInputEventListener = null;
     }
 
     @Override
-    public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+    public boolean dispatchKeyEvent(@Nullable KeyEvent event) {
         if (super.dispatchKeyEvent(event)) {
             return true;
         }
@@ -452,7 +446,6 @@
 
     /**
      * Starts the AD service.
-     * @hide
      */
     public void startAdService() {
         if (DEBUG) {
@@ -493,6 +486,7 @@
      * Sends current video bounds to related TV AD service.
      *
      * @param bounds the rectangle area for rendering the current video.
+     * @hide
      */
     public void sendCurrentVideoBounds(@NonNull Rect bounds) {
         if (DEBUG) {
@@ -508,6 +502,7 @@
      *
      * @param channelUri The current channel URI; {@code null} if there is no currently tuned
      *                   channel.
+     * @hide
      */
     public void sendCurrentChannelUri(@Nullable Uri channelUri) {
         if (DEBUG) {
@@ -520,6 +515,7 @@
 
     /**
      * Sends track info list to related TV AD service.
+     * @hide
      */
     public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
         if (DEBUG) {
@@ -536,6 +532,7 @@
      * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
      *                tuned.
      * @see android.media.tv.TvInputInfo
+     * @hide
      */
     public void sendCurrentTvInputId(@Nullable String inputId) {
         if (DEBUG) {
@@ -577,6 +574,7 @@
      *     can also be added to the params.
      *
      * @see #ERROR_KEY_METHOD_NAME
+     * @hide
      */
     public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
         if (DEBUG) {
@@ -599,6 +597,7 @@
      *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
      *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
      *             how to parse this data.
+     * @hide
      */
     public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
             @NonNull Bundle data) {
@@ -613,7 +612,6 @@
 
     /**
      * Interface definition for a callback to be invoked when the unhandled input event is received.
-     * @hide
      */
     public interface OnUnhandledInputEventListener {
         /**
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 018eaf6..f110705 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -12,4 +12,11 @@
     namespace: "media_tv"
     description: "Enable the TV client-side AD framework."
     bug: "303506816"
+}
+
+flag {
+    name: "tiaf_v_apis"
+    namespace: "media_tv"
+    description: "TIAF V3.0 APIs for Android V"
+    bug: "303323657"
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 498eec6..7cf32ec 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -2634,8 +2634,8 @@
 
         /**
          * This is called when
-         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} is
-         * called.
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+         * is called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param signingId the ID to identify the request.
@@ -2644,7 +2644,6 @@
          * @param host The host of the SSL CLient Authentication Server
          * @param port The port of the SSL Client Authentication Server
          * @param data the original bytes to be signed.
-         * @hide
          */
         public void onRequestSigning(
                 Session session, String signingId, String algorithm, String host,
@@ -2657,7 +2656,6 @@
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
-         * @hide
          */
         public void onRequestCertificate(Session session, String host, int port) {
         }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7b6dc38..eba26d4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -44,6 +45,7 @@
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
 import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
 import android.net.Uri;
 import android.net.http.SslCertificate;
@@ -740,8 +742,8 @@
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
          * @param cert the SSL certificate received.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
         }
 
@@ -894,13 +896,13 @@
         }
 
         /**
-         * Called when video becomes frozen or unfrozen. Audio playback will continue while
-         * video will be frozen to the last frame if {@code true}.
+         * Called when video becomes frozen or unfrozen. Audio playback will continue while video
+         * will be frozen to the last frame if {@code true}.
+         *
          * @param isFrozen Whether or not the video is frozen.
-         * @hide
          */
-        public void onVideoFreezeUpdated(boolean isFrozen) {
-        }
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onVideoFreezeUpdated(boolean isFrozen) {}
 
         /**
          * Called when content is allowed.
@@ -959,12 +961,12 @@
 
         /**
          * Called when the TV App sends the selected track info as a response to
-         * requestSelectedTrackInfo.
+         * {@link #requestSelectedTrackInfo()}
          *
-         * @param tracks
-         * @hide
+         * @param tracks A list of {@link TvTrackInfo} that are currently selected
          */
-        public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
         }
 
         @Override
@@ -1373,13 +1375,13 @@
         }
 
         /**
-         * Requests the currently selected {@link TvTrackInfo} from the TV App.
+         * Requests a list of the currently selected {@link TvTrackInfo} from the TV App.
          *
          * <p> Normally, track info cannot be synchronized until the channel has
-         * been changed. This is used when the session of the TIAS is newly
-         * created and the normal synchronization has not happened yet.
-         * @hide
+         * been changed. This is used when the session of the {@link TvInteractiveAppService}
+         * is newly created and the normal synchronization has not happened yet.
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         @CallSuper
         public void requestSelectedTrackInfo() {
             executeOrPostRunnableOnMainThread(() -> {
@@ -1664,9 +1666,9 @@
          * @see #onSigningResult(String, byte[])
          * @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
          * @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
-         * @hide
          */
         @CallSuper
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
                 @NonNull String host, int port, @NonNull byte[] data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1693,8 +1695,9 @@
          *
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
-         * @hide
          */
+        @CallSuper
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void requestCertificate(@NonNull String host, int port) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 3b29574..29a3b98 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
 import android.media.tv.TvView;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.TvInteractiveAppManager.Session;
 import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
 import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
@@ -585,8 +587,9 @@
     /**
      * Sends the currently selected track info to the TV Interactive App.
      *
-     * @hide
+     * @param tracks list of {@link TvTrackInfo} of the currently selected track(s)
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
         if (DEBUG) {
             Log.d(TAG, "sendSelectedTrackInfo");
@@ -720,12 +723,12 @@
     }
 
     /**
-     * Alerts the TV Interactive app that the video freeze state has been updated.
-     * If {@code true}, the video is frozen on the last frame while audio playback continues.
+     * Alerts the TV Interactive app that the video freeze state has been updated. If {@code true},
+     * the video is frozen on the last frame while audio playback continues.
      *
      * @param isFrozen Whether the video is frozen.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void notifyVideoFreezeUpdated(boolean isFrozen) {
         if (DEBUG) {
             Log.d(TAG, "notifyVideoFreezeUpdated");
@@ -757,12 +760,12 @@
     }
 
     /**
-     * Send the requested SSL certificate to the TV Interactive App
+     * Sends the requested SSL certificate to the TV Interactive App
      * @param host the host name of the SSL authentication server.
      * @param port the port of the SSL authentication server. E.g., 443
      * @param cert the SSL certificate requested
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
         if (DEBUG) {
             Log.d(TAG, "sendCertificate");
@@ -1248,8 +1251,8 @@
          * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
         }
 
@@ -1387,6 +1390,37 @@
         }
 
         /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param signingId the ID to identify the request.
+         * @param algorithm the standard name of the signature algorithm requested, such as
+         *                  MD5withRSA, SHA256withDSA, etc.
+         * @param host The hostname of the SSL authentication server.
+         * @param port The port of the SSL authentication server.
+         * @param data the original bytes to be signed.
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId,
+                @NonNull String algorithm, @NonNull String host, int port, @NonNull byte[] data) {
+        }
+
+        /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestCertificate(String, int)} is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param host The hostname of the SSL authentication server.
+         * @param port The port of the SSL authentication server.
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onRequestCertificate(@NonNull String iAppServiceId, @NonNull String host,
+                int port) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#setTvRecordingInfo(String,
          * TvRecordingInfo)} is called.
          *
@@ -1954,5 +1988,34 @@
                 mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data);
             }
         }
+
+        @Override
+        public void onRequestSigning(
+                Session session, String id, String algorithm, String host, int port, byte[] data) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestSigning");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestSigning - session not created");
+                return;
+            }
+            if (mCallback != null && Flags.tiafVApis()) {
+                mCallback.onRequestSigning(mIAppServiceId, id, algorithm, host, port, data);
+            }
+        }
+
+        @Override
+        public void onRequestCertificate(Session session, String host, int port) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestCertificate");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestCertificate - session not created");
+                return;
+            }
+            if (mCallback != null && Flags.tiafVApis()) {
+                mCallback.onRequestCertificate(mIAppServiceId, host, port);
+            }
+        }
     }
 }
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
index 70202463..c18a2de 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -41,7 +41,7 @@
 
 
     public EffectsTest() {
-        Log.d(TAG, "contructor");
+        Log.d(TAG, "constructor");
     }
 
     @Override
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c572944..279f9d6 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -484,8 +484,9 @@
     WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr);
     VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0)
     VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0)
-    VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0)
+    VALIDATE_INT(workDuration.actualCpuDurationNanos, >= 0)
     VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0)
+    VALIDATE_INT(workDuration.actualGpuDurationNanos + workDuration.actualCpuDurationNanos, > 0)
     return session->reportActualWorkDuration(workDurationPtr);
 }
 
@@ -517,7 +518,7 @@
 void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
                                              int64_t actualCpuDurationNanos) {
     VALIDATE_PTR(aWorkDuration)
-    WARN_INT(actualCpuDurationNanos, > 0)
+    WARN_INT(actualCpuDurationNanos, >= 0)
     static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
 }
 
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 1046d8e9..845a8f9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,10 +64,8 @@
   }
 
   public final class NfcAdapter {
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
     method public void disableForegroundDispatch(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
     method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
@@ -75,6 +73,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method public boolean isEnabled();
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeEnabled();
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
@@ -83,6 +82,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
     method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
     method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setTransactionAllowed(boolean);
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -99,12 +99,12 @@
     field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
     field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
     field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -2147483648; // 0x80000000
     field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
     field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
     field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
     field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -2147483648; // 0x80000000
     field public static final int FLAG_READER_NFC_A = 1; // 0x1
     field public static final int FLAG_READER_NFC_B = 2; // 0x2
     field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
@@ -232,13 +232,13 @@
     method public final void sendResponseApdu(byte[]);
     field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
     field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index f8b640c7..dd2e174 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -14,15 +14,23 @@
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerNfcVendorNciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
     method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+    method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int sendVendorNciMessage(int, @IntRange(from=0, to=15) int, @IntRange(from=0) int, @NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderModePollingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
     method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+    field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
+    field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3
+    field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2
+    field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1; // 0x1
+    field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0; // 0x0
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
     field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
     field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
@@ -36,6 +44,11 @@
     method public boolean onUnlockAttempted(android.nfc.Tag);
   }
 
+  @FlaggedApi("android.nfc.nfc_vendor_cmd") public static interface NfcAdapter.NfcVendorNciCallback {
+    method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciNotification(@IntRange(from=9, to=15) int, int, @NonNull byte[]);
+    method @FlaggedApi("android.nfc.nfc_vendor_cmd") public void onVendorNciResponse(@IntRange(from=0, to=15) int, int, @NonNull byte[]);
+  }
+
   @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
     method public void onWlcStateChanged(@NonNull android.nfc.WlcListenerDeviceInfo);
   }
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 63c3414..c444740 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -24,6 +24,7 @@
 import android.nfc.IAppCallback;
 import android.nfc.INfcAdapterExtras;
 import android.nfc.INfcControllerAlwaysOnListener;
+import android.nfc.INfcVendorNciCallback;
 import android.nfc.INfcTag;
 import android.nfc.INfcCardEmulation;
 import android.nfc.INfcFCardEmulation;
@@ -87,6 +88,7 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
     boolean enableReaderOption(boolean enable);
     boolean isObserveModeSupported();
+    boolean isObserveModeEnabled();
     boolean setObserveMode(boolean enabled);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
@@ -100,4 +102,7 @@
 
     void notifyPollingLoop(in Bundle frame);
     void notifyHceDeactivated();
+    int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload);
+    void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks);
+    void unregisterVendorExtensionCallback(in INfcVendorNciCallback callbacks);
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
similarity index 60%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to nfc/java/android/nfc/INfcVendorNciCallback.aidl
index a368de2..821dc6f 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.nfc;
 
-package com.android.credentialmanager.ui.model
-
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+/**
+ * @hide
+ */
+oneway interface INfcVendorNciCallback {
+    void onVendorResponseReceived(int gid, int oid, in byte[] payload);
+    void onVendorNotificationReceived(int gid, int oid, in byte[] payload);
+}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 4d56c11..252f46f 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -77,6 +78,7 @@
 
     private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
     private final NfcWlcStateListener mNfcWlcStateListener;
+    private final NfcVendorNciCallbackListener mNfcVendorNciCallbackListener;
 
     /**
      * Intent to start an activity when a tag with NDEF payload is discovered.
@@ -416,18 +418,18 @@
     /**
      * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
      * <p>
-     * Setting this flag makes listening to use current flags.
+     * Setting this flag makes listening to keep the current technology configuration.
      */
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_LISTEN_KEEP = -1;
+    public static final int FLAG_LISTEN_KEEP = 0x80000000;
 
     /**
      * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
      * <p>
-     * Setting this flag makes polling to use current flags.
+     * Setting this flag makes polling to keep the current technology configuration.
      */
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_READER_KEEP = -1;
+    public static final int FLAG_READER_KEEP = 0x80000000;
 
     /** @hide */
     public static final int FLAG_USE_ALL_TECH = 0xff;
@@ -866,6 +868,7 @@
         mLock = new Object();
         mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
         mNfcWlcStateListener = new NfcWlcStateListener(getService());
+        mNfcVendorNciCallbackListener = new NfcVendorNciCallbackListener(getService());
     }
 
     /**
@@ -1204,18 +1207,16 @@
         }
     }
 
-   /**
-    * Disables observe mode to allow the transaction to proceed. See
-    * {@link #isObserveModeSupported()} for a description of observe mode and
-    * use {@link #disallowTransaction()} to enable observe mode and block
-    * transactions again.
-    *
-    * @return boolean indicating success or failure.
-    */
+    /**
+     * Returns whether Observe Mode is currently enabled or not.
+     *
+     * @return true if observe mode is enabled, false otherwise.
+     */
+
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean allowTransaction() {
+    public boolean isObserveModeEnabled() {
         try {
-            return sService.setObserveMode(false);
+            return sService.isObserveModeEnabled();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             return false;
@@ -1223,18 +1224,20 @@
     }
 
     /**
-    * Signals that the transaction has completed and observe mode may be
-    * reenabled. See {@link #isObserveModeSupported()} for a description of
-    * observe mode and use {@link #allowTransaction()} to disable observe
-    * mode and allow transactions to proceed.
-    *
-    * @return boolean indicating success or failure.
-    */
+     * Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
+     * and simply observe and notify the APDU service of polling loop frames. See
+     * {@link #isObserveModeSupported()} for a description of observe mode.
+     *
+     * @param allowed true disables observe mode to allow the transaction to proceed while false
+     *                enables observe mode and does not allow transactions to proceed.
+     *
+     * @return boolean indicating success or failure.
+     */
 
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean disallowTransaction() {
+    public boolean setTransactionAllowed(boolean allowed) {
         try {
-            return sService.setObserveMode(true);
+            return sService.setObserveMode(!allowed);
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             return false;
@@ -1801,6 +1804,8 @@
      *
      * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
      * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+     * (if the _KEEP flag is specified the other technology flags shouldn't be set
+     * and are quietly ignored otherwise).
      * Use {@link #FLAG_READER_DISABLE} to disable polling.
      * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
      * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
@@ -1832,6 +1837,15 @@
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
     public void setDiscoveryTechnology(@NonNull Activity activity,
             @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
+
+        // A special treatment of the _KEEP flags
+        if ((listenTechnology & FLAG_LISTEN_KEEP) != 0) {
+            listenTechnology = -1;
+        }
+        if ((pollTechnology & FLAG_READER_KEEP) != 0) {
+            pollTechnology = -1;
+        }
+
         if (listenTechnology == FLAG_LISTEN_DISABLE) {
             synchronized (sLock) {
                 if (!sHasNfcFeature) {
@@ -1858,10 +1872,10 @@
     }
 
     /**
-     * Restore the poll/listen technologies of NFC controller,
+     * Restore the poll/listen technologies of NFC controller to its default state,
      * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
      *
-     * @param activity The Activity that requests to changed technologies.
+     * @param activity The Activity that requested to change technologies.
      */
 
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
@@ -2967,4 +2981,163 @@
             return null;
         }
     }
+
+    /**
+     * Vendor NCI command success.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    public static final int SEND_VENDOR_NCI_STATUS_SUCCESS = 0;
+    /**
+     * Vendor NCI command rejected.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    public static final int SEND_VENDOR_NCI_STATUS_REJECTED = 1;
+    /**
+     * Vendor NCI command corrupted.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED  = 2;
+    /**
+     * Vendor NCI command failed with unknown reason.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            SEND_VENDOR_NCI_STATUS_SUCCESS,
+            SEND_VENDOR_NCI_STATUS_REJECTED,
+            SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED,
+            SEND_VENDOR_NCI_STATUS_FAILED,
+    })
+    @interface SendVendorNciStatus {}
+
+    /**
+     * Message Type for NCI Command.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    public static final int MESSAGE_TYPE_COMMAND = 1;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            MESSAGE_TYPE_COMMAND,
+    })
+    @interface MessageType {}
+
+    /**
+     * Send Vendor specific Nci Messages with custom message type.
+     *
+     * The format of the NCI messages are defined in the NCI specification. The platform is
+     * responsible for fragmenting the payload if necessary.
+     *
+     * Note that mt (message type) is added at the beginning of method parameters as it is more
+     * distinctive than other parameters and was requested from vendor.
+     *
+     * @param mt message Type of the command
+     * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+     *            the NCI specification
+     * @param oid opcode ID of the command. This is left to the OEM / vendor to decide
+     * @param payload containing vendor Nci message payload
+     * @return message send status
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public @SendVendorNciStatus int sendVendorNciMessage(@MessageType int mt,
+            @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid,
+            @NonNull byte[] payload) {
+        Objects.requireNonNull(payload, "Payload must not be null");
+        try {
+            return sService.sendVendorNciMessage(mt, gid, oid, payload);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Register an {@link NfcVendorNciCallback} to listen for Nfc vendor responses and notifications
+     * <p>The provided callback will be invoked by the given {@link Executor}.
+     *
+     * <p>When first registering a callback, the callbacks's
+     * {@link NfcVendorNciCallback#onVendorNciCallBack(byte[])} is immediately invoked to
+     * notify the vendor notification.
+     *
+     * @param executor an {@link Executor} to execute given callback
+     * @param callback user implementation of the {@link NfcVendorNciCallback}
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public void registerNfcVendorNciCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull NfcVendorNciCallback callback) {
+        mNfcVendorNciCallbackListener.register(executor, callback);
+    }
+
+    /**
+     * Unregister the specified {@link NfcVendorNciCallback}
+     *
+     * <p>The same {@link NfcVendorNciCallback} object used when calling
+     * {@link #registerNfcVendorNciCallback(Executor, NfcVendorNciCallback)} must be used.
+     *
+     * <p>Callbacks are automatically unregistered when application process goes away
+     *
+     * @param callback user implementation of the {@link NfcVendorNciCallback}
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) {
+        mNfcVendorNciCallbackListener.unregister(callback);
+    }
+
+    /**
+     * Interface for receiving vendor NCI responses and notifications.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+    public interface NfcVendorNciCallback {
+        /**
+         * Invoked when a vendor specific NCI response is received.
+         *
+         * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+         *            the NCI specification.
+         * @param oid opcode ID of the command. This is left to the OEM / vendor to decide.
+         * @param payload containing vendor Nci message payload.
+         */
+        @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+        void onVendorNciResponse(
+                @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload);
+
+        /**
+         * Invoked when a vendor specific NCI notification is received.
+         *
+         * @param gid group ID of the command. This needs to be one of the vendor reserved GIDs from
+         *            the NCI specification.
+         * @param oid opcode ID of the command. This is left to the OEM / vendor to decide.
+         * @param payload containing vendor Nci message payload.
+         */
+        @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
+        void onVendorNciNotification(
+                @IntRange(from = 9, to = 15) int gid, int oid, @NonNull byte[] payload);
+    }
 }
diff --git a/nfc/java/android/nfc/NfcVendorNciCallbackListener.java b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java
new file mode 100644
index 0000000..742d75f
--- /dev/null
+++ b/nfc/java/android/nfc/NfcVendorNciCallbackListener.java
@@ -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 android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.NfcVendorNciCallback;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public final class NfcVendorNciCallbackListener extends INfcVendorNciCallback.Stub {
+    private static final String TAG = "Nfc.NfcVendorNciCallbacks";
+    private final INfcAdapter mAdapter;
+    private boolean mIsRegistered = false;
+    private final Map<NfcVendorNciCallback, Executor> mCallbackMap = new HashMap<>();
+
+    public NfcVendorNciCallbackListener(@NonNull INfcAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    public void register(@NonNull Executor executor, @NonNull NfcVendorNciCallback callback) {
+        synchronized (this) {
+            if (mCallbackMap.containsKey(callback)) {
+                return;
+            }
+            mCallbackMap.put(callback, executor);
+            if (!mIsRegistered) {
+                try {
+                    mAdapter.registerVendorExtensionCallback(this);
+                    mIsRegistered = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to register adapter state callback");
+                    mCallbackMap.remove(callback);
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    public void unregister(@NonNull NfcVendorNciCallback callback) {
+        synchronized (this) {
+            if (!mCallbackMap.containsKey(callback) || !mIsRegistered) {
+                return;
+            }
+            if (mCallbackMap.size() == 1) {
+                try {
+                    mAdapter.unregisterVendorExtensionCallback(this);
+                    mIsRegistered = false;
+                    mCallbackMap.remove(callback);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to unregister AdapterStateCallback with service");
+                    throw e.rethrowFromSystemServer();
+                }
+            } else {
+                mCallbackMap.remove(callback);
+            }
+        }
+    }
+
+    @Override
+    public void onVendorResponseReceived(int gid, int oid, @NonNull byte[] payload)
+            throws RemoteException {
+        synchronized (this) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                for (NfcVendorNciCallback callback : mCallbackMap.keySet()) {
+                    Executor executor = mCallbackMap.get(callback);
+                    executor.execute(() -> callback.onVendorNciResponse(gid, oid, payload));
+                }
+            } catch (RuntimeException ex) {
+                throw ex;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void onVendorNotificationReceived(int gid, int oid, @NonNull byte[] payload)
+            throws RemoteException {
+        synchronized (this) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                for (NfcVendorNciCallback callback : mCallbackMap.keySet()) {
+                    Executor executor = mCallbackMap.get(callback);
+                    executor.execute(() -> callback.onVendorNciNotification(gid, oid, payload));
+                }
+            } catch (RuntimeException ex) {
+                throw ex;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+}
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 426c5aa..3254a39 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,6 +105,8 @@
 
     private final ArrayList<String> mPollingLoopFilters;
 
+    private final Map<String, Boolean> mAutoTransact;
+
     /**
      * Whether this service should only be started when the device is unlocked.
      */
@@ -173,6 +175,7 @@
         this.mStaticAidGroups = new HashMap<String, AidGroup>();
         this.mDynamicAidGroups = new HashMap<String, AidGroup>();
         this.mPollingLoopFilters = new ArrayList<String>();
+        this.mAutoTransact = new HashMap<String, Boolean>();
         this.mOffHostName = offHost;
         this.mStaticOffHostName = staticOffHost;
         this.mOnHost = onHost;
@@ -287,6 +290,7 @@
             mStaticAidGroups = new HashMap<String, AidGroup>();
             mDynamicAidGroups = new HashMap<String, AidGroup>();
             mPollingLoopFilters = new ArrayList<String>();
+            mAutoTransact = new HashMap<String, Boolean>();
             mOnHost = onHost;
 
             final int depth = parser.getDepth();
@@ -377,6 +381,10 @@
                             a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
                             .toUpperCase(Locale.ROOT);
                     mPollingLoopFilters.add(plf);
+                    boolean autoTransact = a.getBoolean(
+                            com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
+                            false);
+                    mAutoTransact.put(plf, autoTransact);
                     a.recycle();
                 }
             }
@@ -444,6 +452,17 @@
     }
 
     /**
+     * Returns whether this service would like to automatically transact for a given plf.
+     *
+     * @param plf the polling loop filter to query.
+     * @return {@code true} indicating to auto transact, {@code false} indicating to not.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public boolean getShouldAutoTransact(@NonNull String plf) {
+        return mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false);
+    }
+
+    /**
      * Returns a consolidated list of AIDs with prefixes from the AID groups
      * registered by this service. Note that if a service has both
      * a static (manifest-based) AID group for a category and a dynamic
@@ -630,6 +649,21 @@
     }
 
     /**
+     * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will cause the
+     * device to exit observe mode, just as if
+     * {@link android.nfc.NfcAdapter#setTransactionAllowed(boolean)} had been called with true,
+     * allowing transactions to proceed. The matching frame will also be delivered to
+     * {@link HostApduService#processPollingFrames(List)}.
+     *
+     * @param pollingLoopFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
+        mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+        mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
+    }
+
+    /**
      * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
      * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
      * @param pollingLoopFilter this polling loop filter to add.
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 7cd2533..89b0322 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,11 +244,11 @@
     public static final String KEY_DATA = "data";
 
     /**
-     * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+     * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
      * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+    public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
 
     /**
      * POLLING_LOOP_TYPE_A is the value associated with the key
@@ -299,33 +299,33 @@
     public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
 
     /**
-     * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+     * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
      * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      * when the frame type isn't recognized.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+    public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
 
     /**
-     * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+     * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
      * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      * when the frame type isn't recognized.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+    public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
 
     /**
-     * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+     * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
      * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      * when the frame type isn't recognized.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
 
     /**
      * @hide
      */
-    public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+    public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
             "android.nfc.cardemulation.POLLING_FRAMES";
 
     /**
@@ -405,7 +405,7 @@
                 break;
                 case MSG_POLLING_LOOP:
                     ArrayList<Bundle> frames =
-                            msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+                            msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
                             Bundle.class);
                     processPollingFrames(frames);
                     break;
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 01a4570..0a2619c 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -69,3 +69,10 @@
     description: "Flag for NFC set discovery tech API"
     bug: "300351519"
 }
+
+flag {
+    name: "nfc_vendor_cmd"
+    namespace: "nfc"
+    description: "Enable NFC vendor command support"
+    bug: "289879306"
+}
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 1570e0e..31104a0 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -46,14 +46,6 @@
         _nativeClassInit();
     }
 
-    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
-
-    public static native boolean eglPresentationTimeANDROID(
-        EGLDisplay dpy,
-        EGLSurface sur,
-        long time
-    );
-
     /**
      * Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
      *
@@ -83,4 +75,13 @@
     }
 
     private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
+    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
+
+    public static native boolean eglPresentationTimeANDROID(
+        EGLDisplay dpy,
+        EGLSurface sur,
+        long time
+    );
+
 }
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index f6458c2..ce32ec4 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -46,4 +46,6 @@
     ],
 
     platform_apis: true,
+
+    generate_product_characteristics_rro: true,
 }
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 5636266..572a669 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -6,4 +6,11 @@
     description: "Feature flag for recoverability detection"
     bug: "310236690"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_crashrecovery"
+    namespace: "crashrecovery"
+    description: "Enables various dependencies of crashrecovery module"
+    bug: "289203818"
+}
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
index b2af315..c0d93531 100644
--- a/packages/CrashRecovery/framework/Android.bp
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -1,9 +1,55 @@
-filegroup {
+soong_config_module_type {
+    name: "platform_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+platform_filegroup {
     name: "framework-crashrecovery-sources",
     srcs: [
         "java/**/*.java",
         "java/**/*.aidl",
     ],
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [],
+        },
+    },
     path: "java",
     visibility: ["//frameworks/base:__subpackages__"],
 }
+
+soong_config_module_type {
+    name: "module_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+module_filegroup {
+    name: "framework-crashrecovery-module-sources",
+    srcs: [],
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [
+                "java/**/*.java",
+                "java/**/*.aidl",
+            ],
+        },
+    },
+    path: "java",
+    visibility: ["//packages/modules/CrashRecovery/framework"],
+}
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index 27ddff9..ab10b5a 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -1,9 +1,63 @@
-filegroup {
+soong_config_module_type {
+    name: "platform_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+platform_filegroup {
     name: "services-crashrecovery-sources",
     srcs: [
         "java/**/*.java",
         "java/**/*.aidl",
+        ":statslog-crashrecovery-java-gen",
     ],
-    path: "java",
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [],
+        },
+    },
     visibility: ["//frameworks/base:__subpackages__"],
 }
+
+soong_config_module_type {
+    name: "module_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+module_filegroup {
+    name: "services-crashrecovery-module-sources",
+    srcs: [],
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [
+                "java/**/*.java",
+                "java/**/*.aidl",
+                ":statslog-crashrecovery-java-gen",
+            ],
+        },
+    },
+    visibility: ["//packages/modules/CrashRecovery/service"],
+}
+
+genrule {
+    name: "statslog-crashrecovery-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module crashrecovery " +
+        "--javaPackage com.android.server.crashrecovery.proto --javaClass CrashRecoveryStatsLog --worksource",
+    out: ["com/android/server/crashrecovery/proto/CrashRecoveryStatsLog.java"],
+}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dd54334..5d03ef5 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -46,11 +46,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -390,7 +390,8 @@
             return;
         }
 
-        FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+        CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+                level, levelToString(level));
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
         Exception res = null;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 2007079..50322f0 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -39,13 +39,13 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.SystemConfig;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 import com.android.server.pm.ApexManager;
 
 import java.io.BufferedReader;
@@ -418,7 +418,7 @@
 
         final VersionedPackage logPackage = logPackageTemp;
         WatchdogRollbackLogger.logEvent(logPackage,
-                FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+                CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
                 reasonToLog, failedPackageToLog);
 
         Consumer<Intent> onResult = result -> {
@@ -430,19 +430,19 @@
                     int rollbackId = rollback.getRollbackId();
                     saveStagedRollbackId(rollbackId, logPackage);
                     WatchdogRollbackLogger.logEvent(logPackage,
-                            FrameworkStatsLog
+                            CrashRecoveryStatsLog
                             .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
                             reasonToLog, failedPackageToLog);
 
                 } else {
                     WatchdogRollbackLogger.logEvent(logPackage,
-                            FrameworkStatsLog
+                            CrashRecoveryStatsLog
                                     .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
                             reasonToLog, failedPackageToLog);
                 }
             } else {
                 WatchdogRollbackLogger.logEvent(logPackage,
-                        FrameworkStatsLog
+                        CrashRecoveryStatsLog
                                 .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                         reasonToLog, failedPackageToLog);
             }
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index f9ef994..898c543 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -16,16 +16,16 @@
 
 package com.android.server.rollback;
 
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,8 +42,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.PackageWatchdog;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 
 import java.util.List;
 import java.util.Set;
@@ -197,8 +197,8 @@
                 + " rollbackReason: " + rollbackReasonToString(rollbackReason)
                 + " failedPackageName: " + failingPackageName);
         if (logPackage != null) {
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+            CrashRecoveryStatsLog.write(
+                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
                     type,
                     logPackage.getPackageName(),
                     logPackage.getVersionCode(),
@@ -208,8 +208,8 @@
         } else {
             // In the case that the log package is null, still log an empty string as an
             // indication that retrieving the logging parent failed.
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+            CrashRecoveryStatsLog.write(
+                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
                     type,
                     "",
                     0,
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
index 7b235f8..0100718 100644
--- a/packages/CredentialManager/res/drawable/more_horiz_24px.xml
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -3,6 +3,7 @@
     android:height="24dp"
     android:viewportWidth="960"
     android:viewportHeight="960"
+    android:contentDescription="@string/more_options_content_description"
     android:tint="?attr/colorControlNormal">
   <path
       android:fillColor="@android:color/white"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index b97e992..910ff96 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,7 +17,7 @@
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minWidth="@dimen/dropdown_touch_target_min_width"
+                android:minHeight="@dimen/dropdown_touch_target_min_height"
                 android:orientation="horizontal"
                 android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
@@ -28,7 +28,7 @@
         android:layout_height="wrap_content"
         android:layout_centerVertical="true"
         android:layout_alignParentStart="true"
-        android:contentDescription="@string/provider_icon_content_description"
+        android:contentDescription="@string/more_options_content_description"
         android:background="@null"/>
     <TextView
         android:id="@android:id/text1"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 261154f..4bf0e99 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,7 +17,7 @@
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minWidth="@dimen/dropdown_touch_target_min_width"
+                android:minHeight="@dimen/dropdown_touch_target_min_height"
                 android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
 
@@ -25,7 +25,6 @@
             android:id="@android:id/icon1"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:contentDescription="@string/dropdown_presentation_more_sign_in_options_text"
             android:layout_centerVertical="true"
             android:layout_alignParentStart="true"
             android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 53852cb..b47a4dc 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -26,6 +26,6 @@
     <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
     <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
     <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
-    <integer name="autofill_max_visible_datasets">3</integer>
-    <dimen name="dropdown_touch_target_min_width">48dp</dimen>
+    <integer name="autofill_max_visible_datasets">5</integer>
+    <dimen name="dropdown_touch_target_min_height">48dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index f98164b..82b47a9 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -172,5 +172,5 @@
   <!-- Strings for dropdown presentation. -->
   <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
   <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
-  <string name="provider_icon_content_description">Credential provider icon</string>
+  <string name="more_options_content_description">More</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 0fa248d..9a2cf61 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -36,7 +36,7 @@
 
 fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? =
     this.cancelUiRequest?.let { cancelUiRequest ->
-        val showCancel = cancelUiRequest.shouldShowCancellationUi().apply {
+        val showCancel = cancelUiRequest.shouldShowCancellationExplanation().apply {
             Log.d(TAG, "Received UI cancel request, shouldShowCancellationUi: $this")
         }
         if (showCancel) {
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 4155b03..9242141 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -17,7 +17,7 @@
 package com.android.credentialmanager.ktx
 
 import android.content.Intent
-import android.credentials.selection.CancelUiRequest
+import android.credentials.selection.CancelSelectionRequest
 import android.credentials.selection.Constants
 import android.credentials.selection.CreateCredentialProviderData
 import android.credentials.selection.GetCredentialProviderData
@@ -25,10 +25,10 @@
 import android.credentials.selection.RequestInfo
 import android.os.ResultReceiver
 
-val Intent.cancelUiRequest: CancelUiRequest?
+val Intent.cancelUiRequest: CancelSelectionRequest?
     get() = this.extras?.getParcelable(
-        CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
-        CancelUiRequest::class.java
+        CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+        CancelSelectionRequest::class.java
     )
 
 val Intent.requestInfo: RequestInfo?
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 6cafcf7..0ccb07a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.content.Intent
-import android.credentials.selection.CancelUiRequest
+import android.credentials.selection.CancelSelectionRequest
 import android.credentials.selection.Constants
 import android.credentials.selection.CreateCredentialProviderData
 import android.credentials.selection.GetCredentialProviderData
@@ -32,6 +32,7 @@
 import android.os.Bundle
 import android.os.ResultReceiver
 import android.util.Log
+import android.view.autofill.AutofillManager
 import com.android.credentialmanager.createflow.DisabledProviderInfo
 import com.android.credentialmanager.createflow.EnabledProviderInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
@@ -80,9 +81,10 @@
                     CreateCredentialProviderData::class.java
                 ) ?: emptyList()
             RequestInfo.TYPE_GET ->
-                intent.extras?.getParcelableArrayList(
-                    ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
-                    GetCredentialProviderData::class.java
+                getEnabledProviderDataList(
+                    intent
+                ) ?: getEnabledProviderDataListFromAuthExtras(
+                    intent
                 ) ?: emptyList()
             else -> {
                 Log.d(
@@ -106,7 +108,8 @@
         isReqForAllOptions = intent.getBooleanExtra(
                 Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
                 /*defaultValue=*/ false
-        )
+        ) || (requestInfo?.isShowAllOptionsRequested ?: false) // TODO(b/323552850) - Remove
+        // usage on Constants.EXTRA_REQ_FOR_ALL_OPTIONS once it is deprecated.
 
         val cancellationRequest = getCancelUiRequest(intent)
         val cancelUiRequestState = cancellationRequest?.let {
@@ -238,6 +241,24 @@
         )
     }
 
+    private fun getEnabledProviderDataList(intent: Intent): List<GetCredentialProviderData>? {
+        return intent.extras?.getParcelableArrayList(
+            ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+            GetCredentialProviderData::class.java
+        )
+    }
+
+    private fun getEnabledProviderDataListFromAuthExtras(
+        intent: Intent
+    ): List<GetCredentialProviderData>? {
+        return intent.getBundleExtra(
+            AutofillManager.EXTRA_AUTH_STATE
+        ) ?.getParcelableArrayList(
+            ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+            GetCredentialProviderData::class.java
+        )
+    }
+
     // IMPORTANT: new invocation should be mindful that this method can throw.
     private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
         return CreateFlowUtils.toEnabledProviderList(
@@ -275,10 +296,10 @@
         }
 
         /** Return the cancellation request if present. */
-        fun getCancelUiRequest(intent: Intent): CancelUiRequest? {
+        fun getCancelUiRequest(intent: Intent): CancelSelectionRequest? {
             return intent.extras?.getParcelable(
-                CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
-                CancelUiRequest::class.java
+                CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
+                CancelSelectionRequest::class.java
             )
         }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index fa975aa..05aa548 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -131,7 +131,7 @@
             // Cancellation was for a different request, don't cancel the current UI.
             return Triple(true, false, null)
         }
-        val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationUi()
+        val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationExplanation()
         Log.d(
             Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
             " ui = $shouldShowCancellationUi")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 1f1d236..2628f09 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -16,22 +16,24 @@
 
 package com.android.credentialmanager.autofill
 
-import android.R
+import android.app.PendingIntent
 import android.app.assist.AssistStructure
 import android.content.Context
-import android.app.PendingIntent
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialRequest
-import android.credentials.GetCandidateCredentialsResponse
-import android.credentials.GetCandidateCredentialsException
+import android.credentials.Credential
+import android.credentials.CredentialManager
 import android.credentials.CredentialOption
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCredentialRequest
+import android.credentials.GetCredentialResponse
+import android.credentials.selection.Entry
 import android.credentials.selection.GetCredentialProviderData
+import android.credentials.selection.ProviderData
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
 import android.provider.Settings
-import android.credentials.Credential
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
 import android.service.autofill.Field
@@ -44,12 +46,11 @@
 import android.service.autofill.SaveRequest
 import android.service.credentials.CredentialProviderService
 import android.util.Log
+import android.view.autofill.AutofillId
 import android.view.autofill.AutofillValue
 import android.view.autofill.IAutoFillManagerClient
-import android.view.autofill.AutofillId
-import android.widget.inline.InlinePresentationSpec
-import android.credentials.CredentialManager
 import android.widget.RemoteViews
+import android.widget.inline.InlinePresentationSpec
 import androidx.autofill.inline.v1.InlineSuggestionUi
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
@@ -61,10 +62,9 @@
 import com.android.credentialmanager.ktx.credentialEntry
 import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.model.get.ProviderInfo
+import java.util.concurrent.Executors
 import org.json.JSONException
 import org.json.JSONObject
-import java.util.concurrent.Executors
 
 
 class CredentialAutofillService : AutofillService() {
@@ -116,8 +116,10 @@
             return
         }
 
+        val responseClientState = Bundle()
+        responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-                requestId)
+                requestId, responseClientState)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
@@ -154,7 +156,7 @@
                 }
 
                 val fillResponse = convertToFillResponse(result, request,
-                        this@CredentialAutofillService)
+                    responseClientState)
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -231,7 +233,7 @@
     }
 
     private fun getEntryToIconMap(
-            candidateProviderDataList: MutableList<GetCredentialProviderData>
+            candidateProviderDataList: List<GetCredentialProviderData>
     ): Map<String, Icon> {
         val entryIconMap: MutableMap<String, Icon> = mutableMapOf()
         candidateProviderDataList.forEach { provider ->
@@ -262,19 +264,16 @@
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
             filLRequest: FillRequest,
-            context: Context
+            responseClientState: Bundle
     ): FillResponse? {
-        val providerList = GetFlowUtils.toProviderList(
-                getCredResponse.candidateProviderDataList,
-                context)
-        if (providerList.isEmpty()) {
+        val candidateProviders = getCredResponse.candidateProviderDataList
+        if (candidateProviders.isEmpty()) {
             return null
         }
 
-        val entryIconMap: Map<String, Icon> =
-                getEntryToIconMap(getCredResponse.candidateProviderDataList)
-        val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
-                mapAutofillIdToProviders(providerList)
+        val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
+        val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
+                mapAutofillIdToProviders(candidateProviders)
         val fillResponseBuilder = FillResponse.Builder()
         var validFillResponse = false
         autofillIdToProvidersMap.forEach { (autofillId, providers) ->
@@ -286,17 +285,21 @@
         if (!validFillResponse) {
             return null
         }
+        fillResponseBuilder.setClientState(responseClientState)
         return fillResponseBuilder.build()
     }
 
     private fun processProvidersForAutofillId(
             filLRequest: FillRequest,
             autofillId: AutofillId,
-            providerList: List<ProviderInfo>,
+            providerDataList: ArrayList<GetCredentialProviderData>,
             entryIconMap: Map<String, Icon>,
             fillResponseBuilder: FillResponse.Builder,
             bottomSheetPendingIntent: PendingIntent?
     ): Boolean {
+        val providerList = GetFlowUtils.toProviderList(
+            providerDataList,
+            this@CredentialAutofillService)
         if (providerList.isEmpty()) {
             return false
         }
@@ -315,7 +318,7 @@
         maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
         val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
                 Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
-                (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+                (maxDropdownDisplayLimit - 1)).coerceAtMost(totalEntryCount - 1)
 
         var i = 0
         var datasetAdded = false
@@ -340,7 +343,7 @@
                 return@usernameLoop
             }
             if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
-                return@usernameLoop;
+                return@usernameLoop
             }
             val icon: Icon = if (primaryEntry.icon == null) {
                 // The empty entry icon has non-null icon reference but null drawable reference.
@@ -398,19 +401,20 @@
                 inlinePresentationSpecsCount)
         if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
             addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
-                    fillResponseBuilder)
+                    fillResponseBuilder, providerDataList)
         }
         return datasetAdded
     }
 
-    private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
-                                         pendingIntent: PendingIntent,
-                                         icon: Icon,
-                                         spec: InlinePresentationSpec,
-                                         duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
-            InlinePresentation {
-        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
-                && primaryEntry.displayName != null) {
+    private fun createInlinePresentation(
+        primaryEntry: CredentialEntryInfo,
+        pendingIntent: PendingIntent,
+        icon: Icon,
+        spec: InlinePresentationSpec,
+        duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
+    ): InlinePresentation {
+        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+            primaryEntry.displayName != null) {
             primaryEntry.displayName!!
         } else {
             primaryEntry.userName
@@ -430,7 +434,8 @@
     private fun addDropdownMoreOptionsPresentation(
             bottomSheetPendingIntent: PendingIntent,
             autofillId: AutofillId,
-            fillResponseBuilder: FillResponse.Builder) {
+            fillResponseBuilder: FillResponse.Builder
+    ) {
         val presentationBuilder = Presentations.Builder()
                 .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
 
@@ -460,7 +465,8 @@
             bottomSheetPendingIntent: PendingIntent,
             spec: InlinePresentationSpec,
             autofillId: AutofillId,
-            fillResponseBuilder: FillResponse.Builder
+            fillResponseBuilder: FillResponse.Builder,
+            providerDataList: ArrayList<GetCredentialProviderData>
     ) {
         val dataSetBuilder = Dataset.Builder()
         val sliceBuilder = InlineSuggestionUi
@@ -471,6 +477,10 @@
                 .setInlinePresentation(InlinePresentation(
                         sliceBuilder.build().slice, spec, /* pinned= */ true))
 
+        val extraBundle = Bundle()
+        extraBundle.putParcelableArrayList(
+                        ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
+
         fillResponseBuilder.addDataset(
                 dataSetBuilder
                         .setField(
@@ -479,6 +489,7 @@
                                         presentationBuilder.build())
                                         .build())
                         .setAuthentication(bottomSheetPendingIntent.intentSender)
+                        .setAuthenticationExtras(extraBundle)
                         .build()
         )
     }
@@ -514,16 +525,16 @@
      *     }
      */
     private fun mapAutofillIdToProviders(
-            providerList: List<ProviderInfo>
-    ): Map<AutofillId, List<ProviderInfo>> {
-        val autofillIdToProviders: MutableMap<AutofillId, MutableList<ProviderInfo>> =
-                mutableMapOf()
+        providerList: List<GetCredentialProviderData>
+    ): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
+        val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
+            mutableMapOf()
         providerList.forEach { provider ->
             val autofillIdToCredentialEntries:
-                    MutableMap<AutofillId, MutableList<CredentialEntryInfo>> =
-                    mapAutofillIdToCredentialEntries(provider.credentialEntryList)
+                    MutableMap<AutofillId, ArrayList<Entry>> =
+                mapAutofillIdToCredentialEntries(provider.credentialEntries)
             autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
-                autofillIdToProviders.getOrPut(autofillId) { mutableListOf() }
+                autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
                         .add(copyProviderInfo(provider, entries))
             }
         }
@@ -531,13 +542,13 @@
     }
 
     private fun mapAutofillIdToCredentialEntries(
-            credentialEntryList: List<CredentialEntryInfo>
-    ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
+            credentialEntryList: List<Entry>
+    ): MutableMap<AutofillId, ArrayList<Entry>> {
         val autofillIdToCredentialEntries:
-                MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
+                MutableMap<AutofillId, ArrayList<Entry>> = mutableMapOf()
         credentialEntryList.forEach entryLoop@{ credentialEntry ->
             val autofillId: AutofillId? = credentialEntry
-                    .fillInIntent
+                    .frameworkExtrasIntent
                     ?.getParcelableExtra(
                             CredentialProviderService.EXTRA_AUTOFILL_ID,
                             AutofillId::class.java)
@@ -546,24 +557,22 @@
                         " Integration might be disabled.")
                 return@entryLoop
             }
-            autofillIdToCredentialEntries.getOrPut(autofillId) { mutableListOf() }
+            autofillIdToCredentialEntries.getOrPut(autofillId) { ArrayList() }
                     .add(credentialEntry)
         }
         return autofillIdToCredentialEntries
     }
 
     private fun copyProviderInfo(
-            providerInfo: ProviderInfo,
-            credentialList: List<CredentialEntryInfo>
-    ): ProviderInfo {
-        return ProviderInfo(
-                providerInfo.id,
-                providerInfo.icon,
-                providerInfo.displayName,
-                credentialList,
-                providerInfo.authenticationEntryList,
-                providerInfo.remoteEntry,
-                providerInfo.actionEntryList
+            providerInfo: GetCredentialProviderData,
+            credentialList: List<Entry>
+    ): GetCredentialProviderData {
+        return GetCredentialProviderData(
+            providerInfo.providerFlattenedComponentName,
+            credentialList,
+            providerInfo.actionChips,
+            providerInfo.authenticationEntries,
+            providerInfo.remoteEntry
         )
     }
 
@@ -574,10 +583,11 @@
     private fun getCredManRequest(
             structure: AssistStructure,
             sessionId: Int,
-            requestId: Int
+            requestId: Int,
+            responseClientState: Bundle
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        traverseStructure(structure, credentialOptions)
+        traverseStructure(structure, credentialOptions, responseClientState)
 
         if (credentialOptions.isNotEmpty()) {
             val dataBundle = Bundle()
@@ -592,7 +602,8 @@
 
     private fun traverseStructure(
             structure: AssistStructure,
-            cmRequests: MutableList<CredentialOption>
+            cmRequests: MutableList<CredentialOption>,
+            responseClientState: Bundle
     ) {
         val windowNodes: List<AssistStructure.WindowNode> =
                 structure.run {
@@ -600,16 +611,17 @@
                 }
 
         windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
-            traverseNode(windowNode.rootViewNode, cmRequests)
+            traverseNode(windowNode.rootViewNode, cmRequests, responseClientState)
         }
     }
 
     private fun traverseNode(
             viewNode: AssistStructure.ViewNode,
-            cmRequests: MutableList<CredentialOption>
+            cmRequests: MutableList<CredentialOption>,
+            responseClientState: Bundle
     ) {
         viewNode.autofillId?.let {
-            val options = getCredentialOptionsFromViewNode(viewNode, it)
+            val options = getCredentialOptionsFromViewNode(viewNode, it, responseClientState)
             cmRequests.addAll(options)
         }
 
@@ -619,13 +631,14 @@
                 }
 
         children.forEach { childNode: AssistStructure.ViewNode ->
-            traverseNode(childNode, cmRequests)
+            traverseNode(childNode, cmRequests, responseClientState)
         }
     }
 
     private fun getCredentialOptionsFromViewNode(
             viewNode: AssistStructure.ViewNode,
-            autofillId: AutofillId
+            autofillId: AutofillId,
+            responseClientState: Bundle
     ): List<CredentialOption> {
         // TODO(b/293945193) Replace with isCredential check from viewNode
         val credentialHints: MutableList<String> = mutableListOf()
@@ -633,6 +646,9 @@
             for (hint in viewNode.autofillHints!!) {
                 if (hint.startsWith(CRED_HINT_PREFIX)) {
                     credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX))
+                    if (viewNode.webDomain != null) {
+                        responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
+                    }
                 }
             }
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 68f1c86..02afc54 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -74,6 +74,8 @@
                     setMaxHeightMethodName,
                     context.resources.getDimensionPixelSize(
                             com.android.credentialmanager.R.dimen.autofill_icon_size));
+            remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
+                    .providerDisplayName);
             val drawableId =
                     com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
             remoteViews.setInt(
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 109644f..be7e448 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -21,11 +21,16 @@
   <!-- Title of a screen prompting if the user would like to use their saved passkey.
   [CHAR LIMIT=80] -->
   <string name="use_passkey_title">Use passkey?</string>
-  <!-- Title of a screen prompting if the user would like to use their saved password.
+  <!-- Title of a screen prompting if the user would like to use their saved passkey.
+[CHAR LIMIT=80] -->
+  <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string>
+  <!-- Title of a screen prompting if the user would like to sign in with provider
   [CHAR LIMIT=80] -->
   <string name="use_password_title">Use password?</string>
-  <!-- Content description for the cancel button of a screen. [CHAR LIMIT=NONE] -->
-  <string name="dialog_cancel_button_cd">Cancel</string>
-  <!-- Content description for the OK button of a screen. [CHAR LIMIT=NONE] -->
-  <string name="dialog_ok_button_cd">OK</string>
+  <!-- Content description for the dismiss button of a screen. [CHAR LIMIT=NONE] -->
+  <string name="dialog_dismiss_button">Dismiss</string>
+  <!-- Content description for the continue button of a screen. [CHAR LIMIT=NONE] -->
+  <string name="dialog_continue_button">Continue</string>
+  <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] -->
+  <string name="dialog_sign_in_options_button">Sign-in Options</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 790f5b1..f8e22ee 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -58,9 +58,9 @@
 
         scrollable(Screen.SinglePasswordScreen.route) {
             SinglePasswordScreen(
-                state = viewModel.uiState.value as SingleEntry,
+                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+                screenIcon = null,
                 columnState = it.columnState,
-                onCloseApp = onCloseApp,
             )
         }
     }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
index c20ee0c..b2812d3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
@@ -30,26 +30,28 @@
 
 @Composable
 fun AccountRow(
-    name: String,
-    email: String,
+    primaryText: String,
+    secondaryText: String? = null,
     modifier: Modifier = Modifier,
 ) {
     Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
         Text(
-            text = name,
+            text = primaryText,
             color = Color(0xFFE6FF7B),
             overflow = TextOverflow.Ellipsis,
             maxLines = 1,
             style = MaterialTheme.typography.title2
         )
-        Text(
-            text = email,
-            modifier = Modifier.padding(top = 7.dp),
-            color = Color(0xFFCAC5BC),
-            overflow = TextOverflow.Ellipsis,
-            maxLines = 2,
-            style = MaterialTheme.typography.body1,
-        )
+        if (secondaryText != null) {
+            Text(
+                text = secondaryText,
+                modifier = Modifier.padding(top = 7.dp),
+                color = Color(0xFFCAC5BC),
+                overflow = TextOverflow.Ellipsis,
+                maxLines = 2,
+                style = MaterialTheme.typography.body1,
+            )
+        }
     }
 }
 
@@ -57,7 +59,7 @@
 @Composable
 fun AccountRowPreview() {
     AccountRow(
-        name = "Elisa Beckett",
-        email = "beckett_bakery@gmail.com",
+        primaryText = "Elisa Beckett",
+        secondaryText = "beckett_bakery@gmail.com",
     )
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
new file mode 100644
index 0000000..3297991
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.credentialmanager.ui.components
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Chip
+import androidx.wear.compose.material.ChipColors
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.Text
+import com.android.credentialmanager.R
+import com.android.credentialmanager.ui.components.CredentialsScreenChip.TOPPADDING
+
+@Composable
+fun CredentialsScreenChip(
+    label: String,
+    onClick: () -> Unit,
+    secondaryLabel: String? = null,
+    icon: Drawable? = null,
+    modifier: Modifier = Modifier,
+    colors: ChipColors = ChipDefaults.secondaryChipColors(),
+) {
+    val labelParam: (@Composable RowScope.() -> Unit) =
+        {
+            Text(
+                text = label,
+                modifier = Modifier.fillMaxWidth(),
+                textAlign = TextAlign.Center,
+                overflow = TextOverflow.Ellipsis,
+                maxLines = if (secondaryLabel != null) 1 else 2,
+            )
+        }
+
+    val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+        secondaryLabel?.let {
+            {
+                Text(
+                    text = secondaryLabel,
+                    overflow = TextOverflow.Ellipsis,
+                    maxLines = 1,
+                )
+            }
+        }
+
+    val iconParam: (@Composable BoxScope.() -> Unit)? =
+        icon?.let {
+            {
+                 ChipDefaults.IconSize
+            }
+        }
+
+    Chip(
+        label = labelParam,
+        onClick = onClick,
+        modifier = modifier,
+        secondaryLabel = secondaryLabelParam,
+        icon = iconParam,
+        colors = colors,
+        enabled = true,
+    )
+}
+
+@Preview
+@Composable
+fun CredentialsScreenChipPreview() {
+    CredentialsScreenChip(
+        label = "Elisa Beckett",
+        onClick = { },
+        secondaryLabel = "beckett_bakery@gmail.com",
+        icon = null,
+        modifier = Modifier
+            .clipToBounds()
+            .padding(top = 2.dp)
+    )
+}
+
+@Composable
+fun SignInOptionsChip(onClick: () -> Unit) {
+    CredentialsScreenChip(
+        label = stringResource(R.string.dialog_sign_in_options_button),
+        onClick = onClick,
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(top = TOPPADDING)
+    )
+}
+
+@Preview
+@Composable
+fun SignInOptionsChipPreview() {
+    SignInOptionsChip({})
+}
+
+@Composable
+fun ContinueChip(onClick: () -> Unit) {
+    CredentialsScreenChip(
+        label = stringResource(R.string.dialog_continue_button),
+        onClick = onClick,
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(top = TOPPADDING),
+        colors = ChipDefaults.primaryChipColors(),
+    )
+}
+
+@Preview
+@Composable
+fun ContinueChipPreview() {
+    ContinueChip({})
+}
+
+@Composable
+fun DismissChip(onClick: () -> Unit) {
+    CredentialsScreenChip(
+        label = stringResource(R.string.dialog_dismiss_button),
+        onClick = onClick,
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(top = TOPPADDING),
+    )
+}
+
+@Preview
+@Composable
+fun DismissChipPreview() {
+    DismissChip({})
+}
+
+private object CredentialsScreenChip {
+    val TOPPADDING = 8.dp
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt
deleted file mode 100644
index 5cb3c15..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt
+++ /dev/null
@@ -1,71 +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
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.components
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.ButtonDefaults
-import com.google.android.horologist.compose.material.Button
-import com.google.android.horologist.compose.tools.WearPreview
-import com.android.credentialmanager.R
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun DialogButtonsRow(
-    onCancelClick: () -> Unit,
-    onOKClick: () -> Unit,
-    modifier: Modifier = Modifier,
-    cancelButtonIcon: ImageVector = Icons.Default.Close,
-    okButtonIcon: ImageVector = Icons.Default.Check,
-    cancelButtonContentDescription: String = stringResource(R.string.dialog_cancel_button_cd),
-    okButtonContentDescription: String = stringResource(R.string.dialog_ok_button_cd),
-) {
-    Row(
-        modifier = modifier,
-        horizontalArrangement = Arrangement.Center,
-    ) {
-        Button(
-            imageVector = cancelButtonIcon,
-            contentDescription = cancelButtonContentDescription,
-            onClick = onCancelClick,
-            colors = ButtonDefaults.secondaryButtonColors(),
-        )
-        Button(
-            imageVector = okButtonIcon,
-            contentDescription = okButtonContentDescription,
-            onClick = onOKClick,
-            modifier = Modifier.padding(start = 20.dp)
-        )
-    }
-}
-
-@WearPreview
-@Composable
-fun DialogButtonsRowPreview() {
-    DialogButtonsRow(onCancelClick = {}, onOKClick = {})
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
index 956c56b..1ddf4af 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
@@ -16,45 +16,24 @@
 
 package com.android.credentialmanager.ui.components
 
-import androidx.annotation.DrawableRes
+import android.graphics.drawable.Drawable
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.MaterialTheme
+import androidx.core.graphics.drawable.toBitmap
 import androidx.wear.compose.material.Text
-import com.android.credentialmanager.R
-import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.compose.material.Icon
-import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
-import com.google.android.horologist.compose.tools.WearPreview
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SignInHeader(
-    @DrawableRes icon: Int,
-    title: String,
-    modifier: Modifier = Modifier,
-) {
-    SignInHeader(
-        iconContent = {
-            Icon(
-                id = icon,
-                contentDescription = DECORATIVE_ELEMENT_CONTENT_DESCRIPTION
-            )
-        },
-        title = title,
-        modifier = modifier,
-    )
-}
+import androidx.compose.material3.Icon
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
+import androidx.compose.ui.text.style.TextAlign
 
 @Composable
 fun SignInHeader(
-    iconContent: @Composable ColumnScope.() -> Unit,
+    icon: Drawable?,
     title: String,
     modifier: Modifier = Modifier,
 ) {
@@ -62,22 +41,22 @@
         modifier = modifier,
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
-        iconContent()
+        if (icon != null) {
+            Icon(
+                bitmap = icon.toBitmap().asImageBitmap(),
+                modifier = Modifier.size(32.dp),
+                // Decorative purpose only.
+                contentDescription = null
+            )
+        }
+
         Text(
             text = title,
+            textAlign = TextAlign.Center,
             modifier = Modifier
                 .padding(top = 6.dp)
                 .padding(horizontal = 10.dp),
-            style = MaterialTheme.typography.title3
+            style = WearMaterialTheme.typography.title3
         )
     }
 }
-
-@WearPreview
-@Composable
-fun SignInHeaderPreview() {
-    SignInHeader(
-        icon = R.drawable.passkey_icon,
-        title = stringResource(R.string.use_passkey_title)
-    )
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
index 8532783..e94dd68 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt
@@ -19,21 +19,12 @@
 package com.android.credentialmanager.ui.screens.single
 
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
 import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
-import com.android.credentialmanager.R
-import com.android.credentialmanager.ui.components.AccountRow
-import com.android.credentialmanager.ui.components.DialogButtonsRow
-import com.android.credentialmanager.ui.components.SignInHeader
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumn
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
 
 @Composable
 fun SingleAccountScreen(
@@ -51,33 +42,4 @@
         item { accountContent() }
         content()
     }
-}
-
-@WearPreview
-@Composable
-fun SingleAccountScreenPreview() {
-    SingleAccountScreen(
-        headerContent = {
-            SignInHeader(
-                icon = R.drawable.passkey_icon,
-                title = stringResource(R.string.use_passkey_title),
-            )
-        },
-        accountContent = {
-            AccountRow(
-                name = "Elisa Beckett",
-                email = "beckett_bakery@gmail.com",
-                modifier = Modifier.padding(top = 10.dp)
-            )
-        },
-        columnState = belowTimeTextPreview(),
-    ) {
-        item {
-            DialogButtonsRow(
-                onCancelClick = {},
-                onOKClick = {},
-                modifier = Modifier.padding(top = 10.dp)
-            )
-        }
-    }
-}
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt
new file mode 100644
index 0000000..42a88e2
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.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.credentialmanager.ui.screens.single
+
+import androidx.activity.result.IntentSenderRequest
+
+sealed class UiState {
+    data object CredentialScreen : UiState()
+
+    data class CredentialSelected(
+        val intentSenderRequest: IntentSenderRequest?
+    ) : UiState()
+
+    data object Cancel : UiState()
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index c9b0230..92d8a39 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -18,66 +18,119 @@
 
 package com.android.credentialmanager.ui.screens.single.passkey
 
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.Column
+import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.R
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.AccountRow
-import com.android.credentialmanager.ui.components.DialogButtonsRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.ui.screens.single.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
 
+@OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SinglePasskeyScreen(
-    name: String,
-    email: String,
-    onCancelClick: () -> Unit,
-    onOKClick: () -> Unit,
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
+    viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
+    navController: NavHostController = rememberNavController(),
+) {
+    viewModel.initialize(credentialSelectorUiState.entry)
+
+    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+    when (val state = uiState) {
+        UiState.CredentialScreen -> {
+            SinglePasskeyScreen(
+                credentialSelectorUiState.entry,
+                screenIcon,
+                columnState,
+                modifier,
+                viewModel
+            )
+        }
+
+        is UiState.CredentialSelected -> {
+            val launcher = rememberLauncherForActivityResult(
+                StartBalIntentSenderForResultContract()
+            ) {
+                viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
+            }
+
+            SideEffect {
+                state.intentSenderRequest?.let {
+                    launcher.launch(it)
+                }
+            }
+        }
+
+        UiState.Cancel -> {
+            // TODO(b/322797032) add valid navigation path here for going back
+            navController.popBackStack()
+        }
+    }
+}
+
+@OptIn(ExperimentalHorologistApi::class)
+@Composable
+fun SinglePasskeyScreen(
+    entry: CredentialEntryInfo,
+    screenIcon: Drawable?,
+    columnState: ScalingLazyColumnState,
+    modifier: Modifier = Modifier,
+    viewModel: SinglePasskeyScreenViewModel,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
-                icon = R.drawable.passkey_icon,
+                icon = screenIcon,
                 title = stringResource(R.string.use_passkey_title),
             )
         },
         accountContent = {
-            AccountRow(
-                name = name,
-                email = email,
-                modifier = Modifier.padding(top = 10.dp),
-            )
+            if (entry.displayName != null) {
+                AccountRow(
+                    primaryText = checkNotNull(entry.displayName),
+                    secondaryText = entry.userName,
+                    modifier = Modifier.padding(top = 10.dp),
+                )
+            } else {
+                AccountRow(
+                    primaryText = entry.userName,
+                    modifier = Modifier.padding(top = 10.dp),
+                )
+            }
         },
         columnState = columnState,
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
-            DialogButtonsRow(
-                onCancelClick = onCancelClick,
-                onOKClick = onOKClick,
-                modifier = Modifier.padding(top = 10.dp)
-            )
+            Column {
+                ContinueChip(viewModel::onContinueClick)
+                SignInOptionsChip(viewModel::onSignInOptionsClick)
+                DismissChip(viewModel::onDismissClick)
+            }
         }
     }
 }
-
-@WearPreview
-@Composable
-fun SinglePasskeyScreenPreview() {
-    SinglePasskeyScreen(
-        name = "Elisa Beckett",
-        email = "beckett_bakery@gmail.com",
-        onCancelClick = {},
-        onOKClick = {},
-        columnState = belowTimeTextPreview(),
-    )
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
new file mode 100644
index 0000000..35c39f6
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ui.screens.single.passkey
+
+import android.content.Intent
+import android.credentials.selection.UserSelectionDialogResult
+import android.credentials.selection.ProviderPendingIntentResponse
+import androidx.annotation.MainThread
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.ktx.getIntentSenderRequest
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import dagger.hilt.android.lifecycle.HiltViewModel
+import com.android.credentialmanager.ui.screens.single.UiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+@HiltViewModel
+class SinglePasskeyScreenViewModel @Inject constructor(
+    private val credentialManagerClient: CredentialManagerClient,
+) : ViewModel() {
+
+    private val _uiState =
+        MutableStateFlow<UiState>(UiState.CredentialScreen)
+    val uiState: StateFlow<UiState> = _uiState
+
+    private lateinit var requestGet: Request.Get
+    private lateinit var entryInfo: CredentialEntryInfo
+
+
+    @MainThread
+    fun initialize(entry: CredentialEntryInfo) {
+        this.entryInfo = entry
+    }
+
+    fun onDismissClick() {
+        _uiState.value = UiState.Cancel
+    }
+
+    fun onContinueClick() {
+        _uiState.value = UiState.CredentialSelected(
+            intentSenderRequest = entryInfo.getIntentSenderRequest()
+        )
+    }
+
+    fun onSignInOptionsClick() {
+    }
+
+    fun onPasskeyInfoRetrieved(
+        resultCode: Int? = null,
+        resultData: Intent? = null,
+    ) {
+        val userSelectionDialogResult = UserSelectionDialogResult(
+            requestGet.token,
+            entryInfo.providerId,
+            entryInfo.entryKey,
+            entryInfo.entrySubkey,
+            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+        )
+        credentialManagerClient.sendResult(userSelectionDialogResult)
+    }
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 9f971ae..a8be944 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -18,8 +18,9 @@
 
 package com.android.credentialmanager.ui.screens.single.password
 
-import android.util.Log
+import android.graphics.drawable.Drawable
 import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
@@ -29,52 +30,52 @@
 import androidx.compose.ui.unit.dp
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
 import com.android.credentialmanager.R
-import com.android.credentialmanager.TAG
 import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.ui.components.DialogButtonsRow
 import com.android.credentialmanager.ui.components.PasswordRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
-import com.android.credentialmanager.ui.model.PasswordUiModel
+import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.ui.screens.single.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
-import com.google.android.horologist.compose.tools.WearPreview
 
+@OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SinglePasswordScreen(
-    state: SingleEntry,
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
-    onCloseApp: () -> Unit,
     modifier: Modifier = Modifier,
     viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
+    navController: NavHostController = rememberNavController(),
 ) {
-    viewModel.initialize(state.entry)
+    viewModel.initialize(credentialSelectorUiState.entry)
 
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()
 
     when (val state = uiState) {
-        SinglePasswordScreenUiState.Idle -> {
-            // TODO: b/301206470 implement latency version of the screen
-        }
-
-        is SinglePasswordScreenUiState.Loaded -> {
+        UiState.CredentialScreen -> {
             SinglePasswordScreen(
-                passwordUiModel = state.passwordUiModel,
-                onCancelClick = viewModel::onCancelClick,
-                onOKClick = viewModel::onOKClick,
-                columnState = columnState,
-                modifier = modifier
+                credentialSelectorUiState.entry,
+                screenIcon,
+                columnState,
+                modifier,
+                viewModel
             )
         }
 
-        is SinglePasswordScreenUiState.PasswordSelected -> {
+        is UiState.CredentialSelected -> {
             val launcher = rememberLauncherForActivityResult(
                 StartBalIntentSenderForResultContract()
             ) {
-                viewModel.onPasswordInfoRetrieved(it.resultCode, it.data)
+                viewModel.onPasswordInfoRetrieved(it.resultCode, null)
             }
 
             SideEffect {
@@ -84,39 +85,32 @@
             }
         }
 
-        SinglePasswordScreenUiState.Cancel -> {
-            // TODO: b/301206470 implement navigation for when user taps cancel
-        }
-
-        SinglePasswordScreenUiState.Error -> {
-            // TODO: b/301206470 implement navigation for when there is an error to load screen
-        }
-
-        SinglePasswordScreenUiState.Completed -> {
-            Log.d(TAG, "Received signal to finish the activity.")
-            onCloseApp()
+        UiState.Cancel -> {
+            // TODO(b/322797032) add valid navigation path here for going back
+            navController.popBackStack()
         }
     }
 }
 
+@OptIn(ExperimentalHorologistApi::class)
 @Composable
-fun SinglePasswordScreen(
-    passwordUiModel: PasswordUiModel,
-    onCancelClick: () -> Unit,
-    onOKClick: () -> Unit,
+private fun SinglePasswordScreen(
+    entry: CredentialEntryInfo,
+    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
+    viewModel: SinglePasswordScreenViewModel,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
-                icon = R.drawable.passkey_icon,
+                icon = screenIcon,
                 title = stringResource(R.string.use_password_title),
             )
         },
         accountContent = {
             PasswordRow(
-                email = passwordUiModel.email,
+                email = entry.userName,
                 modifier = Modifier.padding(top = 10.dp),
             )
         },
@@ -124,23 +118,13 @@
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
-            DialogButtonsRow(
-                onCancelClick = onCancelClick,
-                onOKClick = onOKClick,
-                modifier = Modifier.padding(top = 10.dp)
-            )
+            Column {
+                ContinueChip(viewModel::onContinueClick)
+                SignInOptionsChip(viewModel::onSignInOptionsClick)
+                DismissChip(viewModel::onDismissClick)
+            }
         }
     }
 }
 
-@WearPreview
-@Composable
-fun SinglePasswordScreenPreview() {
-    SinglePasswordScreen(
-        passwordUiModel = PasswordUiModel(email = "beckett_bakery@gmail.com"),
-        onCancelClick = {},
-        onOKClick = {},
-        columnState = belowTimeTextPreview(),
-    )
-}
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index c9c66b4..3f841b8 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -17,16 +17,15 @@
 package com.android.credentialmanager.ui.screens.single.password
 
 import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
 import android.credentials.selection.UserSelectionDialogResult
-import androidx.activity.result.IntentSenderRequest
+import android.credentials.selection.ProviderPendingIntentResponse
 import androidx.annotation.MainThread
 import androidx.lifecycle.ViewModel
 import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.client.CredentialManagerClient
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.model.PasswordUiModel
+import com.android.credentialmanager.ui.screens.single.UiState
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -37,36 +36,31 @@
     private val credentialManagerClient: CredentialManagerClient,
 ) : ViewModel() {
 
-    private var initializeCalled = false
-
     private lateinit var requestGet: Request.Get
     private lateinit var entryInfo: CredentialEntryInfo
 
     private val _uiState =
-        MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle)
-    val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState
+        MutableStateFlow<UiState>(UiState.CredentialScreen)
+    val uiState: StateFlow<UiState> = _uiState
 
     @MainThread
     fun initialize(entryInfo: CredentialEntryInfo) {
-        if (initializeCalled) return
-        initializeCalled = true
-        _uiState.value = SinglePasswordScreenUiState.Loaded(
-            PasswordUiModel(
-                email = entryInfo.userName,
-            )
-        )
+        this.entryInfo = entryInfo
     }
 
-    fun onCancelClick() {
-        _uiState.value = SinglePasswordScreenUiState.Cancel
+    fun onDismissClick() {
+        _uiState.value = UiState.Cancel
     }
 
-    fun onOKClick() {
-        _uiState.value = SinglePasswordScreenUiState.PasswordSelected(
+    fun onContinueClick() {
+        _uiState.value = UiState.CredentialSelected(
             intentSenderRequest = entryInfo.getIntentSenderRequest()
         )
     }
 
+    fun onSignInOptionsClick() {
+    }
+
     fun onPasswordInfoRetrieved(
         resultCode: Int? = null,
         resultData: Intent? = null,
@@ -79,18 +73,5 @@
             if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
         )
         credentialManagerClient.sendResult(userSelectionDialogResult)
-        _uiState.value = SinglePasswordScreenUiState.Completed
     }
 }
-
-sealed class SinglePasswordScreenUiState {
-    data object Idle : SinglePasswordScreenUiState()
-    data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState()
-    data class PasswordSelected(
-        val intentSenderRequest: IntentSenderRequest?
-    ) : SinglePasswordScreenUiState()
-
-    data object Cancel : SinglePasswordScreenUiState()
-    data object Error : SinglePasswordScreenUiState()
-    data object Completed : SinglePasswordScreenUiState()
-}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 09e0d61..bf69d3b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -45,11 +45,9 @@
 
         <activity android:name=".v2.ui.InstallLaunch"
             android:configChanges="orientation|keyboardHidden|screenSize"
-            android:theme="@style/Theme.AlertDialogActivity"
             android:exported="false"/>
 
         <activity android:name=".InstallStart"
-                android:theme="@style/Theme.AlertDialogActivity"
                 android:exported="true"
                 android:excludeFromRecents="true">
             <intent-filter android:priority="1">
@@ -79,14 +77,12 @@
                 android:exported="false" />
 
         <activity android:name=".DeleteStagedFileOnResult"
-            android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
             android:exported="false" />
 
         <activity android:name=".PackageInstallerActivity"
                 android:exported="false" />
 
         <activity android:name=".InstallInstalling"
-                android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
                 android:exported="false" />
 
         <receiver android:name=".common.InstallEventReceiver"
@@ -98,16 +94,13 @@
         </receiver>
 
         <activity android:name=".InstallSuccess"
-                android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
                 android:exported="false" />
 
         <activity android:name=".InstallFailed"
-                android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
                 android:exported="false" />
 
         <activity android:name=".UninstallerActivity"
                 android:configChanges="orientation|keyboardHidden|screenSize"
-                android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
                 android:excludeFromRecents="true"
                 android:noHistory="true"
                 android:exported="true">
@@ -121,7 +114,6 @@
 
         <activity android:name=".v2.ui.UninstallLaunch"
             android:configChanges="orientation|keyboardHidden|screenSize"
-            android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
             android:excludeFromRecents="true"
             android:noHistory="true"
             android:exported="false">
@@ -144,7 +136,6 @@
         </receiver>
 
         <activity android:name=".UninstallUninstalling"
-            android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
             android:excludeFromRecents="true"
             android:exported="false" />
 
@@ -171,7 +162,6 @@
 
         <activity android:name=".UnarchiveActivity"
                   android:configChanges="orientation|keyboardHidden|screenSize"
-                  android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
                   android:excludeFromRecents="true"
                   android:noHistory="true"
                   android:exported="true">
@@ -183,7 +173,6 @@
 
         <activity android:name=".UnarchiveErrorActivity"
                   android:configChanges="orientation|keyboardHidden|screenSize"
-                  android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
                   android:excludeFromRecents="true"
                   android:noHistory="true"
                   android:exported="true">
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f4641b9..f425f52 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -272,7 +272,7 @@
     <!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
          after parts of the app have been previously moved into the cloud for temporary storage.
          "installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
-    <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+    <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%2$s</xliff:g>?</string>
     <!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
     <string name="unarchive_body_text">This app will begin to download in the background</string>
     <!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 9a06229..f5af510 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -17,19 +17,12 @@
 
 <resources>
 
-    <style name="Theme.AlertDialogActivity.NoAnimation"
-           parent="@style/Theme.AlertDialogActivity.NoActionBar">
-        <item name="android:windowAnimationStyle">@null</item>
-    </style>
-
     <style name="Theme.AlertDialogActivity"
         parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
         <item name="alertDialogStyle">@style/AlertDialog</item>
-    </style>
-
-    <style name="Theme.AlertDialogActivity.NoActionBar">
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
+        <item name="android:windowAnimationStyle">@null</item>
     </style>
 
 </resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 9af799c..b20117d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -25,10 +25,12 @@
 import android.app.Fragment;
 import android.app.FragmentTransaction;
 import android.content.IntentSender;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Process;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -97,14 +99,30 @@
             String appTitle = pm.getApplicationInfo(mPackageName,
                     PackageManager.ApplicationInfoFlags.of(
                             MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
-            // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
-            //  archived apps.
-            showDialogFragment(appTitle, "installerTitle");
+            String installerTitle = getResponsibleInstallerTitle(pm,
+                    pm.getInstallSourceInfo(mPackageName));
+            showDialogFragment(appTitle, installerTitle);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Invalid packageName: " + e.getMessage());
         }
     }
 
+    private String getResponsibleInstallerTitle(PackageManager pm,
+            InstallSourceInfo installSource)
+            throws PackageManager.NameNotFoundException {
+        String packageName = TextUtils.isEmpty(installSource.getUpdateOwnerPackageName())
+                ? installSource.getInstallingPackageName()
+                : installSource.getUpdateOwnerPackageName();
+        if (packageName == null) {
+            // Should be unreachable.
+            Log.e(TAG, "Installer not found.");
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return "";
+        }
+        return pm.getApplicationInfo(packageName, /* flags= */ 0).loadLabel(pm).toString();
+    }
+
     @NonNull
     private String[] getRequestedPermissions(String callingPackage) {
         String[] requestedPermissions = null;
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 6f9556f..ec519ca 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.6.0-rc01"
+    extra["jetpackComposeVersion"] = "1.7.0-alpha01"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 1f78a9c..f6fbc02 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
 #
 
 [versions]
-agp = "8.2.1"
-compose-compiler = "1.5.1"
+agp = "8.2.2"
+compose-compiler = "1.5.8"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
-kotlin = "1.9.0"
+kotlin = "1.9.22"
 truth = "1.1.5"
 
 [libraries]
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 618dc37..08a8797 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-beta02")
+    api("androidx.compose.material3:material3:1.2.0-rc01")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.7.6")
+    api("androidx.navigation:navigation-compose:2.8.0-alpha01")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.7.0-alpha03")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt
new file mode 100644
index 0000000..1d3eb51
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/lifecycle/FlowExt.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.lifecycle
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import kotlinx.coroutines.flow.Flow
+
+@Composable
+fun <T> Flow<T>.collectAsCallbackWithLifecycle(): () -> T? {
+    val value by collectAsStateWithLifecycle(initialValue = null)
+    return { value }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index b34c310..e704505 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
@@ -95,56 +94,60 @@
         if (options.isNotEmpty()) {
             ExposedDropdownMenu(
                 expanded = expanded,
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .width(with(LocalDensity.current) { dropDownWidth.toDp() }),
+                modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
                 onDismissRequest = { expanded = false },
             ) {
                 options.forEachIndexed { index, option ->
-                    TextButton(
-                        modifier = Modifier
-                            .fillMaxHeight()
-                            .fillMaxWidth(),
-                        onClick = {
-                            if (selectedOptionsState.contains(index)) {
-                                if (index == allIndex)
-                                    selectedOptionsState.clear()
-                                else {
-                                    selectedOptionsState.remove(
-                                        index
-                                    )
-                                    if (selectedOptionsState.contains(allIndex))
-                                        selectedOptionsState.remove(
-                                            allIndex
-                                        )
-                                }
-                            } else {
-                                selectedOptionsState.add(
-                                    index
-                                )
-                            }
-                            onSelectedOptionStateChange()
-                        }) {
-                        Row(
-                            modifier = Modifier
-                                .fillMaxHeight()
-                                .fillMaxWidth(),
-                            horizontalArrangement = Arrangement.Start,
-                            verticalAlignment = Alignment.CenterVertically
-                        ) {
-                            Checkbox(
-                                checked = selectedOptionsState.contains(index),
-                                onCheckedChange = null,
-                            )
-                            Text(text = option)
-                        }
-                    }
+                    CheckboxItem(
+                        selectedOptionsState,
+                        index,
+                        allIndex,
+                        onSelectedOptionStateChange,
+                        option,
+                    )
                 }
             }
         }
     }
 }
 
+@Composable
+private fun CheckboxItem(
+    selectedOptionsState: SnapshotStateList<Int>,
+    index: Int,
+    allIndex: Int,
+    onSelectedOptionStateChange: () -> Unit,
+    option: String
+) {
+    TextButton(
+        modifier = Modifier.fillMaxWidth(),
+        onClick = {
+            if (selectedOptionsState.contains(index)) {
+                if (index == allIndex) {
+                    selectedOptionsState.clear()
+                } else {
+                    selectedOptionsState.remove(index)
+                    selectedOptionsState.remove(allIndex)
+                }
+            } else {
+                selectedOptionsState.add(index)
+            }
+            onSelectedOptionStateChange()
+        }) {
+        Row(
+            modifier = Modifier.fillMaxWidth(),
+            horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Checkbox(
+                checked = selectedOptionsState.contains(index),
+                onCheckedChange = null,
+            )
+            Text(text = option)
+        }
+    }
+}
+
 @Preview
 @Composable
 private fun ActionButtonsPreview() {
@@ -158,4 +161,4 @@
             enabled = true,
             onSelectedOptionStateChange = {})
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt
new file mode 100644
index 0000000..de915ef
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/lifecycle/FlowExtTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.lifecycle
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.waitUntilExists
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FlowExtTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun collectAsCallbackWithLifecycle() {
+        val flow = flowOf(TEXT)
+
+        composeTestRule.setContent {
+            val callback = flow.collectAsCallbackWithLifecycle()
+            Text(callback() ?: "")
+        }
+
+        composeTestRule.waitUntilExists(hasText(TEXT))
+    }
+
+    @Test
+    fun collectAsCallbackWithLifecycle_changed() {
+        val flow = MutableStateFlow(TEXT)
+
+        composeTestRule.setContent {
+            val callback = flow.collectAsCallbackWithLifecycle()
+            Text(callback() ?: "")
+        }
+        flow.value = NEW_TEXT
+
+        composeTestRule.waitUntilExists(hasText(NEW_TEXT))
+    }
+
+    private companion object {
+        const val TEXT = "Text"
+        const val NEW_TEXT = "New Text"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 0a98791..988afd7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,10 +17,11 @@
 package com.android.settingslib.spaprivileged.model.app
 
 import android.content.Context
-import android.content.pm.FeatureFlags
-import android.content.pm.FeatureFlagsImpl
 import android.content.Intent
 import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
+import android.content.pm.Flags
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
@@ -85,13 +86,7 @@
         loadInstantApps: Boolean,
         matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
-        val hiddenSystemModulesDeferred = async {
-            packageManager.getInstalledModules(0)
-                .filter { it.isHidden }
-                .map { it.packageName }
-                .filterNotNull()
-                .toSet()
-        }
+        val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
         val hideWhenDisabledPackagesDeferred = async {
             context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
         }
@@ -205,6 +200,15 @@
     private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean =
         app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages
 
+    private fun PackageManager.getHiddenSystemModules(): Set<String> {
+        val moduleInfos = getInstalledModules(0).filter { it.isHidden }
+        val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet()
+        if (Flags.provideInfoOfApkInApex()) {
+            hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames }
+        }
+        return hiddenApps
+    }
+
     companion object {
         private fun ApplicationInfo.isInAppList(
             showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 9f33fcb..6e9bde4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.spaprivileged.model.app
 
-import android.app.AppOpsManager;
+import android.app.AppOpsManager
 import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.MODE_ERRORED
 import android.app.AppOpsManager.Mode
@@ -24,14 +24,13 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.os.UserHandle
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.map
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 interface IAppOpsController {
-    val mode: LiveData<Int>
-    val isAllowed: LiveData<Boolean>
+    val mode: Flow<Int>
+    val isAllowed: Flow<Boolean>
         get() = mode.map { it == MODE_ALLOWED }
 
     fun setAllowed(allowed: Boolean)
@@ -48,9 +47,7 @@
 ) : IAppOpsController {
     private val appOpsManager = context.appOpsManager
     private val packageManager = context.packageManager
-
-    override val mode: LiveData<Int>
-        get() = _mode
+    override val mode = appOpsManager.opModeFlow(op, app)
 
     override fun setAllowed(allowed: Boolean) {
         val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
@@ -68,15 +65,7 @@
                     PackageManager.FLAG_PERMISSION_USER_SET,
                     UserHandle.getUserHandleForUid(app.uid))
         }
-        _mode.postValue(mode)
     }
 
-    @Mode override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
-
-    private val _mode =
-        object : MutableLiveData<Int>() {
-            override fun onActive() {
-                postValue(getMode())
-            }
-        }
+    @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt
new file mode 100644
index 0000000..0b5604e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.pm.ApplicationInfo
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+fun AppOpsManager.getOpMode(op: Int, app: ApplicationInfo) =
+    checkOpNoThrow(op, app.uid, app.packageName)
+
+fun AppOpsManager.opModeFlow(op: Int, app: ApplicationInfo) =
+    opChangedFlow(op, app).map { getOpMode(op, app) }.flowOn(Dispatchers.Default)
+
+private fun AppOpsManager.opChangedFlow(op: Int, app: ApplicationInfo) = callbackFlow {
+    val listener = object : AppOpsManager.OnOpChangedListener {
+        override fun onOpChanged(op: String, packageName: String) {}
+
+        override fun onOpChanged(op: String, packageName: String, userId: Int) {
+            if (userId == app.userId) trySend(Unit)
+        }
+    }
+    startWatchingMode(op, app.packageName, listener)
+    trySend(Unit)
+
+    awaitClose { stopWatchingMode(listener) }
+}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 25c3bc5..5db5eae 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -20,7 +20,7 @@
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spa.framework.util.filterItem
 import com.android.settingslib.spaprivileged.model.app.AppOpsController
@@ -166,7 +166,7 @@
         return { true }
     }
 
-    val mode = appOpsController.mode.observeAsState()
+    val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null)
     return {
         when (mode.value) {
             null -> null
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index a28ebc6..458fcc9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -35,5 +35,6 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
+        "flag-junit",
     ],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index f292231..efd53a4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.FakeFeatureFlagsImpl
 import android.content.pm.Flags
+import android.content.pm.ModuleInfo
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.PackageManager.ResolveInfoFlags
@@ -28,6 +29,7 @@
 import android.content.pm.UserInfo
 import android.content.res.Resources
 import android.os.UserManager
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.R
@@ -35,6 +37,7 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
@@ -50,6 +53,9 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
+    @get:Rule
+    val mSetFlagsRule = SetFlagsRule()
+
     private val resources = mock<Resources> {
         on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
     }
@@ -273,6 +279,38 @@
     }
 
     @Test
+    fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
+        mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+        packageManager.stub {
+            on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+        }
+        mockInstalledApplications(
+            listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+            ADMIN_USER_ID
+        )
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+    }
+
+    @Test
+    fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
+        mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+        packageManager.stub {
+            on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+        }
+        mockInstalledApplications(
+            listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+            ADMIN_USER_ID
+        )
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP)
+    }
+
+    @Test
     fun showSystemPredicate_showSystem() = runTest {
         val app = SYSTEM_APP
 
@@ -402,6 +440,20 @@
             isArchived = true
         }
 
+        val HIDDEN_APEX_APP = ApplicationInfo().apply {
+            packageName = "hidden.apex.package"
+        }
+
+        val HIDDEN_MODULE_APP = ApplicationInfo().apply {
+            packageName = "hidden.module.package"
+        }
+
+        val HIDDEN_MODULE = ModuleInfo().apply {
+            packageName = "hidden.module.package"
+            apkInApexPackageNames = listOf("hidden.apex.package")
+            isHidden = true
+        }
+
         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
                 this.packageName = packageName
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
new file mode 100644
index 0000000..97c7441
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.UserHandle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsRepositoryTest {
+
+    private var listener: AppOpsManager.OnOpChangedListener? = null
+
+    private val mockAppOpsManager = mock<AppOpsManager> {
+        on {
+            checkOpNoThrow(AppOpsManager.OP_MANAGE_MEDIA, UID, PACKAGE_NAME)
+        } doReturn AppOpsManager.MODE_ERRORED
+
+        on {
+            startWatchingMode(eq(AppOpsManager.OP_MANAGE_MEDIA), eq(PACKAGE_NAME), any())
+        } doAnswer { listener = it.arguments[2] as AppOpsManager.OnOpChangedListener }
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { appOpsManager } doReturn mockAppOpsManager
+    }
+
+    @Test
+    fun getOpMode() {
+        val mode = context.appOpsManager.getOpMode(AppOpsManager.OP_MANAGE_MEDIA, APP)
+
+        assertThat(mode).isEqualTo(AppOpsManager.MODE_ERRORED)
+    }
+
+    @Test
+    fun opModeFlow() = runBlocking {
+        val flow = context.appOpsManager.opModeFlow(AppOpsManager.OP_MANAGE_MEDIA, APP)
+
+        val mode = flow.first()
+
+        assertThat(mode).isEqualTo(AppOpsManager.MODE_ERRORED)
+    }
+
+    @Test
+    fun opModeFlow_changed() = runBlocking {
+        val listDeferred = async {
+            context.appOpsManager.opModeFlow(AppOpsManager.OP_MANAGE_MEDIA, APP).toListWithTimeout()
+        }
+        delay(100)
+
+        mockAppOpsManager.stub {
+            on { checkOpNoThrow(AppOpsManager.OP_MANAGE_MEDIA, UID, PACKAGE_NAME) } doReturn
+                AppOpsManager.MODE_IGNORED
+        }
+        listener?.onOpChanged("", "", UserHandle.getUserId(UID))
+
+        assertThat(listDeferred.await()).contains(AppOpsManager.MODE_IGNORED)
+    }
+
+    private companion object {
+        const val UID = 110000
+        const val PACKAGE_NAME = "package.name"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index d158a24..bb25cf3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -21,7 +21,6 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.lifecycle.MutableLiveData
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
@@ -330,7 +329,7 @@
 private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
     var setAllowedCalledWith: Boolean? = null
 
-    override val mode = MutableLiveData(fakeMode)
+    override val mode = flowOf(fakeMode)
 
     override fun setAllowed(allowed: Boolean) {
         setAllowedCalledWith = allowed
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index fd2f9bd..bab6781 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -7,3 +7,13 @@
     bug: "314812750"
 }
 
+flag {
+    name: "enable_cached_bluetooth_device_dedup"
+    namespace: "bluetooth"
+    description: "Enable dedup in CachedBluetoothDevice"
+    bug: "319197962"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 2ded3c6..89d6ac3 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -38,6 +38,17 @@
             android:src="@drawable/add_a_photo_circled"
             android:layout_gravity="bottom|right"/>
     </FrameLayout>
+    <TextView
+        android:id="@+id/edit_user_info_message"
+        android:gravity="center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="6dp"
+        android:layout_marginEnd="6dp"
+        android:layout_marginTop="24dp"
+        android:textAppearance="@style/android:TextAppearance.Material.Body1"
+        android:text="@string/edit_user_info_message"
+        />
 
     <EditText
         android:id="@+id/user_name"
@@ -46,6 +57,8 @@
         android:layout_gravity="center"
         android:minWidth="200dp"
         android:layout_marginStart="6dp"
+        android:layout_marginEnd="6dp"
+        android:layout_marginTop="24dp"
         android:minHeight="@dimen/min_tap_target_size"
         android:ellipsize="end"
         android:singleLine="true"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4b3af9..1092a16 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1340,11 +1340,11 @@
     <string name="notice_header" translatable="false"></string>
 
     <!-- Name of the phone device. [CHAR LIMIT=30] -->
-    <string name="media_transfer_this_device_name" product="default">This phone</string>
+    <string name="media_transfer_this_device_name">This phone</string>
     <!-- Name of the tablet device. [CHAR LIMIT=30] -->
-    <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+    <string name="media_transfer_this_device_name_tablet">This tablet</string>
     <!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
-    <string name="media_transfer_this_device_name" product="tv">@string/tv_media_transfer_default</string>
+    <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</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] -->
@@ -1484,6 +1484,9 @@
     <!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]-->
     <string name="user_nickname">Nickname</string>
 
+    <!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]-->
+    <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string>
+
     <!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
     <string name="user_add_user">Add user</string>
     <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index fb14a17..58e0a89 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -41,6 +41,7 @@
 import android.os.UserManager;
 import android.print.PrintManager;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
@@ -66,18 +67,29 @@
 import com.android.settingslib.utils.BuildCompatUtils;
 
 import java.text.NumberFormat;
+import java.time.Duration;
 import java.util.List;
 
 public class Utils {
 
     private static final String TAG = "Utils";
 
-    @VisibleForTesting
-    static final String STORAGE_MANAGER_ENABLED_PROPERTY =
-            "ro.storage_manager.enabled";
-
     public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
             "incompatible_charger_warning_disabled";
+    public static final String WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP =
+            "wireless_charging_notification_timestamp";
+
+    @VisibleForTesting
+    static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
+
+    @VisibleForTesting static final long WIRELESS_CHARGING_DEFAULT_TIMESTAMP = -1L;
+
+    @VisibleForTesting
+    static final long WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS =
+            Duration.ofDays(30).toMillis();
+
+    @VisibleForTesting
+    static final String WIRELESS_CHARGING_WARNING_ENABLED = "wireless_charging_warning_enabled";
 
     private static Signature[] sSystemSignature;
     private static String sPermissionControllerPackageName;
@@ -101,19 +113,19 @@
         R.drawable.ic_show_x_wifi_signal_4
     };
 
-    public static void updateLocationEnabled(Context context, boolean enabled, int userId,
-            int source) {
+    /** Update the location enable state. */
+    public static void updateLocationEnabled(
+            @NonNull Context context, boolean enabled, int userId, int source) {
         Settings.Secure.putIntForUser(
-                context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
-                userId);
+                context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source, userId);
 
         LocationManager locationManager = context.getSystemService(LocationManager.class);
         locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
     }
 
     /**
-     * Return string resource that best describes combination of tethering
-     * options available on this device.
+     * Return string resource that best describes combination of tethering options available on this
+     * device.
      */
     public static int getTetheringLabel(TetheringManager tm) {
         String[] usbRegexs = tm.getTetherableUsbRegexs();
@@ -141,14 +153,12 @@
         }
     }
 
-    /**
-     * Returns a label for the user, in the form of "User: user name" or "Work profile".
-     */
+    /** Returns a label for the user, in the form of "User: user name" or "Work profile". */
     public static String getUserLabel(Context context, UserInfo info) {
         String name = info != null ? info.name : null;
         if (info.isManagedProfile()) {
             // We use predefined values for managed profiles
-            return  BuildCompatUtils.isAtLeastT()
+            return BuildCompatUtils.isAtLeastT()
                     ? getUpdatableManagedUserTitle(context)
                     : context.getString(R.string.managed_user_title);
         } else if (info.isGuest()) {
@@ -164,14 +174,14 @@
 
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static String getUpdatableManagedUserTitle(Context context) {
-        return context.getSystemService(DevicePolicyManager.class).getResources().getString(
-                WORK_PROFILE_USER_LABEL,
-                () -> context.getString(R.string.managed_user_title));
+        return context.getSystemService(DevicePolicyManager.class)
+                .getResources()
+                .getString(
+                        WORK_PROFILE_USER_LABEL,
+                        () -> context.getString(R.string.managed_user_title));
     }
 
-    /**
-     * Returns a circular icon for a user.
-     */
+    /** Returns a circular icon for a user. */
     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
         final int iconSize = UserIconDrawable.getDefaultSize(context);
         if (user.isManagedProfile()) {
@@ -185,12 +195,14 @@
                 return new UserIconDrawable(iconSize).setIcon(icon).bake();
             }
         }
-        return new UserIconDrawable(iconSize).setIconDrawable(
-                UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+        return new UserIconDrawable(iconSize)
+                .setIconDrawable(
+                        UserIcons.getDefaultUserIcon(
+                                context.getResources(), user.id, /* light= */ false))
                 .bake();
     }
 
-    /** Formats a double from 0.0..100.0 with an option to round **/
+    /** Formats a double from 0.0..100.0 with an option to round */
     public static String formatPercentage(double percentage, boolean round) {
         final int localPercentage = round ? Math.round((float) percentage) : (int) percentage;
         return formatPercentage(localPercentage);
@@ -222,23 +234,27 @@
      *
      * @param context the context
      * @param batteryChangedIntent battery broadcast intent received from {@link
-     *                             Intent.ACTION_BATTERY_CHANGED}.
+     *     Intent.ACTION_BATTERY_CHANGED}.
      * @param compactStatus to present compact battery charging string if {@code true}
      * @return battery status string
      */
-    public static String getBatteryStatus(Context context, Intent batteryChangedIntent,
-            boolean compactStatus) {
-        final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_UNKNOWN);
+    @NonNull
+    public static String getBatteryStatus(
+            @NonNull Context context, @NonNull Intent batteryChangedIntent, boolean compactStatus) {
+        final int status =
+                batteryChangedIntent.getIntExtra(
+                        BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
         final Resources res = context.getResources();
 
         String statusString = res.getString(R.string.battery_info_status_unknown);
         final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
 
         if (batteryStatus.isCharged()) {
-            statusString = res.getString(compactStatus
-                    ? R.string.battery_info_status_full_charged
-                    : R.string.battery_info_status_full);
+            statusString =
+                    res.getString(
+                            compactStatus
+                                    ? R.string.battery_info_status_full_charged
+                                    : R.string.battery_info_status_full);
         } else {
             if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                 if (compactStatus) {
@@ -246,12 +262,12 @@
                 } else if (batteryStatus.isPluggedInWired()) {
                     switch (batteryStatus.getChargingSpeed(context)) {
                         case BatteryStatus.CHARGING_FAST:
-                            statusString = res.getString(
-                                    R.string.battery_info_status_charging_fast);
+                            statusString =
+                                    res.getString(R.string.battery_info_status_charging_fast);
                             break;
                         case BatteryStatus.CHARGING_SLOWLY:
-                            statusString = res.getString(
-                                    R.string.battery_info_status_charging_slow);
+                            statusString =
+                                    res.getString(R.string.battery_info_status_charging_slow);
                             break;
                         default:
                             statusString = res.getString(R.string.battery_info_status_charging);
@@ -311,7 +327,7 @@
 
     @ColorInt
     public static int applyAlphaAttr(Context context, int attr, int inputColor) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         float alpha = ta.getFloat(0, 0);
         ta.recycle();
         return applyAlpha(alpha, inputColor);
@@ -320,7 +336,10 @@
     @ColorInt
     public static int applyAlpha(float alpha, int inputColor) {
         alpha *= Color.alpha(inputColor);
-        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+        return Color.argb(
+                (int) (alpha),
+                Color.red(inputColor),
+                Color.green(inputColor),
                 Color.blue(inputColor));
     }
 
@@ -329,19 +348,17 @@
         return getColorAttrDefaultColor(context, attr, 0);
     }
 
-    /**
-     * Get color styled attribute {@code attr}, default to {@code defValue} if not found.
-     */
+    /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */
     @ColorInt
     public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         @ColorInt int colorAccent = ta.getColor(0, defValue);
         ta.recycle();
         return colorAccent;
     }
 
     public static ColorStateList getColorAttr(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         ColorStateList stateList = null;
         try {
             stateList = ta.getColorStateList(0);
@@ -356,35 +373,38 @@
     }
 
     public static int getThemeAttr(Context context, int attr, int defaultValue) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         int theme = ta.getResourceId(0, defaultValue);
         ta.recycle();
         return theme;
     }
 
     public static Drawable getDrawable(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         Drawable drawable = ta.getDrawable(0);
         ta.recycle();
         return drawable;
     }
 
     /**
-    * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
-    * preserves the alpha for a given drawable
-    * @param color
-    * @return a color matrix that uses the source alpha and given color
-    */
+     * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
+     * preserves the alpha for a given drawable
+     *
+     * @return a color matrix that uses the source alpha and given color
+     */
     public static ColorMatrix getAlphaInvariantColorMatrixForColor(@ColorInt int color) {
         int r = Color.red(color);
         int g = Color.green(color);
         int b = Color.blue(color);
 
-        ColorMatrix cm = new ColorMatrix(new float[] {
-                0, 0, 0, 0, r,
-                0, 0, 0, 0, g,
-                0, 0, 0, 0, b,
-                0, 0, 0, 1, 0 });
+        ColorMatrix cm =
+                new ColorMatrix(
+                        new float[] {
+                            0, 0, 0, 0, r,
+                            0, 0, 0, 0, g,
+                            0, 0, 0, 0, b,
+                            0, 0, 0, 1, 0
+                        });
 
         return cm;
     }
@@ -393,7 +413,7 @@
      * Create a ColorMatrixColorFilter to tint a drawable but retain its alpha characteristics
      *
      * @return a ColorMatrixColorFilter which changes the color of the output but is invariant on
-     * the source alpha
+     *     the source alpha
      */
     public static ColorFilter getAlphaInvariantColorFilterForColor(@ColorInt int color) {
         return new ColorMatrixColorFilter(getAlphaInvariantColorMatrixForColor(color));
@@ -402,16 +422,17 @@
     /**
      * Determine whether a package is a "system package", in which case certain things (like
      * disabling notifications or disabling the package altogether) should be disallowed.
-     * <p>
-     * Note: This function is just for UI treatment, and should not be used for security purposes.
      *
-     * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and
-     * {@link #isEssentialPackage} instead.
+     * <p>Note: This function is just for UI treatment, and should not be used for security
+     * purposes.
+     *
+     * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and {@link
+     *     #isEssentialPackage} instead.
      */
     @Deprecated
     public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
         if (sSystemSignature == null) {
-            sSystemSignature = new Signature[]{getSystemSignature(pm)};
+            sSystemSignature = new Signature[] {getSystemSignature(pm)};
         }
         return (sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)))
                 || isEssentialPackage(resources, pm, pkg.packageName);
@@ -435,8 +456,8 @@
 
     /**
      * Determine whether a package is a "essential package".
-     * <p>
-     * In which case certain things (like disabling the package) should be disallowed.
+     *
+     * <p>In which case certain things (like disabling the package) should be disallowed.
      */
     public static boolean isEssentialPackage(
             Resources resources, PackageManager pm, String packageName) {
@@ -462,14 +483,12 @@
      * returns {@code false}.
      */
     public static boolean isDeviceProvisioningPackage(Resources resources, String packageName) {
-        String deviceProvisioningPackage = resources.getString(
-                com.android.internal.R.string.config_deviceProvisioningPackage);
+        String deviceProvisioningPackage =
+                resources.getString(com.android.internal.R.string.config_deviceProvisioningPackage);
         return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
     }
 
-    /**
-     * Fetch the package name of the default WebView provider.
-     */
+    /** Fetch the package name of the default WebView provider. */
     @Nullable
     private static String getDefaultWebViewPackageName() {
         if (sDefaultWebViewPackageName != null) {
@@ -503,8 +522,8 @@
     /**
      * Returns the Wifi icon resource for a given RSSI level.
      *
-     * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
-     *              signal icon to users.
+     * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x signal
+     *     icon to users.
      * @param level The number of bars to show (0-4)
      * @throws IllegalArgumentException if an invalid RSSI level is given.
      */
@@ -520,10 +539,7 @@
         try {
             defaultDays =
                     resources.getInteger(
-                            com.android
-                                    .internal
-                                    .R
-                                    .integer
+                            com.android.internal.R.integer
                                     .config_storageManagerDaystoRetainDefault);
         } catch (Resources.NotFoundException e) {
             // We are likely in a test environment.
@@ -535,7 +551,7 @@
         return !context.getSystemService(TelephonyManager.class).isDataCapable();
     }
 
-    /** Returns if the automatic storage management feature is turned on or not. **/
+    /** Returns if the automatic storage management feature is turned on or not. */
     public static boolean isStorageManagerEnabled(Context context) {
         boolean isDefaultOn;
         try {
@@ -543,15 +559,14 @@
         } catch (Resources.NotFoundException e) {
             isDefaultOn = false;
         }
-        return Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
-                isDefaultOn ? 1 : 0)
+        return Settings.Secure.getInt(
+                        context.getContentResolver(),
+                        Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+                        isDefaultOn ? 1 : 0)
                 != 0;
     }
 
-    /**
-     * get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status.
-     */
+    /** get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status. */
     public static boolean isAudioModeOngoingCall(Context context) {
         final AudioManager audioManager = context.getSystemService(AudioManager.class);
         final int audioMode = audioManager.getMode();
@@ -561,8 +576,8 @@
     }
 
     /**
-     * Return the service state is in-service or not.
-     * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+     * Return the service state is in-service or not. To make behavior consistent with SystemUI and
+     * Settings/AboutPhone/SIM status UI
      *
      * @param serviceState Service state. {@link ServiceState}
      */
@@ -581,13 +596,12 @@
     }
 
     /**
-     * Return the combined service state.
-     * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI.
+     * Return the combined service state. To make behavior consistent with SystemUI and
+     * Settings/AboutPhone/SIM status UI.
      *
-     * This method returns a single service state int if either the voice reg state is
-     * {@link ServiceState#STATE_IN_SERVICE} or if data network is registered via a
-     * WWAN transport type. We consider the combined service state of an IWLAN network
-     * to be OOS.
+     * <p>This method returns a single service state int if either the voice reg state is {@link
+     * ServiceState#STATE_IN_SERVICE} or if data network is registered via a WWAN transport type. We
+     * consider the combined service state of an IWLAN network to be OOS.
      *
      * @param serviceState Service state. {@link ServiceState}
      */
@@ -618,9 +632,10 @@
     // on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
     // query the WWAN network directly and check for its registration state
     private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
-        final NetworkRegistrationInfo networkRegWwan = serviceState.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        final NetworkRegistrationInfo networkRegWwan =
+                serviceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         if (networkRegWwan == null) {
             return false;
@@ -633,8 +648,8 @@
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
         int userType = UserIconInfo.TYPE_MAIN;
         try {
-            UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
-                    user.getIdentifier());
+            UserInfo ui =
+                    context.getSystemService(UserManager.class).getUserInfo(user.getIdentifier());
             if (ui != null) {
                 if (ui.isCloneProfile()) {
                     userType = UserIconInfo.TYPE_CLONED;
@@ -650,15 +665,16 @@
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
             return iconFactory
                     .createBadgedIconBitmap(
-                            icon,
-                            new IconOptions().setUser(new UserIconInfo(user, userType)))
+                            icon, new IconOptions().setUser(new UserIconInfo(user, userType)))
                     .newIcon(context);
         }
     }
 
     /** Get the {@link Drawable} that represents the app icon */
     public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
-        return getBadgedIcon(context, appInfo.loadUnbadgedIcon(context.getPackageManager()),
+        return getBadgedIcon(
+                context,
+                appInfo.loadUnbadgedIcon(context.getPackageManager()),
                 UserHandle.getUserHandleForUid(appInfo.uid));
     }
 
@@ -669,10 +685,11 @@
      * @param source bitmap to apply round corner.
      * @param cornerRadius corner radius value.
      */
-    public static Bitmap convertCornerRadiusBitmap(@NonNull Context context,
-            @NonNull Bitmap source, @NonNull float cornerRadius) {
-        final Bitmap roundedBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(),
-                Bitmap.Config.ARGB_8888);
+    @NonNull
+    public static Bitmap convertCornerRadiusBitmap(
+            @NonNull Context context, @NonNull Bitmap source, @NonNull float cornerRadius) {
+        final Bitmap roundedBitmap =
+                Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
         final RoundedBitmapDrawable drawable =
                 RoundedBitmapDrawableFactory.create(context.getResources(), source);
         drawable.setAntiAlias(true);
@@ -687,9 +704,6 @@
      * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the
      * input NetworkCapabilities is not for a VCN network with underlying WiFi network.
      *
-     * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated
-     *   off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}.
-     *
      * @param networkCapabilities NetworkCapabilities of the network.
      */
     @Nullable
@@ -708,8 +722,9 @@
         // Avoid the caller doesn't have permission to read the "Settings.Secure" data.
         try {
             // Whether the incompatible charger warning is disabled or not
-            if (Settings.Secure.getInt(context.getContentResolver(),
-                    INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0) == 1) {
+            if (Settings.Secure.getInt(
+                            context.getContentResolver(), INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0)
+                    == 1) {
                 Log.d(tag, "containsIncompatibleChargers: disabled");
                 return false;
             }
@@ -718,8 +733,7 @@
             return false;
         }
 
-        final List<UsbPort> usbPortList =
-                context.getSystemService(UsbManager.class).getPorts();
+        final List<UsbPort> usbPortList = context.getSystemService(UsbManager.class).getPorts();
         if (usbPortList == null || usbPortList.isEmpty()) {
             return false;
         }
@@ -760,4 +774,85 @@
         return false;
     }
 
+    /** Whether to show the wireless charging notification. */
+    public static boolean shouldShowWirelessChargingNotification(
+            @NonNull Context context, @NonNull String tag) {
+        try {
+            return shouldShowWirelessChargingNotificationInternal(context, tag);
+        } catch (Exception e) {
+            Log.e(tag, "shouldShowWirelessChargingNotification()", e);
+            return false;
+        }
+    }
+
+    /** Stores the timestamp of the wireless charging notification. */
+    public static void updateWirelessChargingNotificationTimestamp(
+            @NonNull Context context, long timestamp, @NonNull String tag) {
+        try {
+            Secure.putLong(
+                    context.getContentResolver(),
+                    WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+                    timestamp);
+        } catch (Exception e) {
+            Log.e(tag, "setWirelessChargingNotificationTimestamp()", e);
+        }
+    }
+
+    /** Whether to show the wireless charging warning in Settings. */
+    public static boolean shouldShowWirelessChargingWarningTip(
+            @NonNull Context context, @NonNull String tag) {
+        try {
+            return Secure.getInt(context.getContentResolver(), WIRELESS_CHARGING_WARNING_ENABLED, 0)
+                    == 1;
+        } catch (Exception e) {
+            Log.e(tag, "shouldShowWirelessChargingWarningTip()", e);
+        }
+        return false;
+    }
+
+    /** Stores the state of whether the wireless charging warning in Settings is enabled. */
+    public static void updateWirelessChargingWarningEnabled(
+            @NonNull Context context, boolean enabled, @NonNull String tag) {
+        try {
+            Secure.putInt(
+                    context.getContentResolver(),
+                    WIRELESS_CHARGING_WARNING_ENABLED,
+                    enabled ? 1 : 0);
+        } catch (Exception e) {
+            Log.e(tag, "setWirelessChargingWarningEnabled()", e);
+        }
+    }
+
+    private static boolean shouldShowWirelessChargingNotificationInternal(
+            @NonNull Context context, @NonNull String tag) {
+        final long lastNotificationTimeMillis =
+                Secure.getLong(
+                        context.getContentResolver(),
+                        WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+                        WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+        if (isWirelessChargingNotificationDisabled(lastNotificationTimeMillis)) {
+            return false;
+        }
+        if (isInitialWirelessChargingNotification(lastNotificationTimeMillis)) {
+            updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+            updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+            return true;
+        }
+        final long durationMillis = System.currentTimeMillis() - lastNotificationTimeMillis;
+        final boolean show = durationMillis > WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS;
+        Log.d(tag, "shouldShowWirelessChargingNotification = " + show);
+        if (show) {
+            updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+            updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+        }
+        return show;
+    }
+
+    private static boolean isWirelessChargingNotificationDisabled(long lastNotificationTimeMillis) {
+        return lastNotificationTimeMillis == Long.MIN_VALUE;
+    }
+
+    private static boolean isInitialWirelessChargingNotification(long lastNotificationTimeMillis) {
+        return lastNotificationTimeMillis == WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 07de7fd..c482995 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -164,12 +165,16 @@
 
         try {
             final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
-            // Check if the package is contained in an APEX. There is no public API to properly
-            // check whether a given APK package comes from an APEX registered as module.
-            // Therefore we conservatively assume that any package scanned from an /apex path is
-            // a system package.
-            return pkg.applicationInfo.sourceDir.startsWith(
-                    Environment.getApexDirectory().getAbsolutePath());
+            if (Flags.provideInfoOfApkInApex()) {
+                return pkg.getApexPackageName() != null;
+            } else {
+                // Check if the package is contained in an APEX. There is no public API to properly
+                // check whether a given APK package comes from an APEX registered as module.
+                // Therefore we conservatively assume that any package scanned from an /apex path is
+                // a system package.
+                return pkg.applicationInfo.sourceDir.startsWith(
+                        Environment.getApexDirectory().getAbsolutePath());
+            }
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 8e1067f..e3012cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.ModuleInfo;
@@ -226,6 +227,11 @@
         final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
         for (ModuleInfo info : moduleInfos) {
             mSystemModules.put(info.getPackageName(), info.isHidden());
+            if (Flags.provideInfoOfApkInApex()) {
+                for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
+                    mSystemModules.put(apkInApexPackageName, info.isHidden());
+                }
+            }
         }
 
         /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index c97445f..647fcb9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static com.android.settingslib.flags.Flags.enableCachedBluetoothDeviceDedup;
+
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -377,6 +379,10 @@
                 cachedDevice = mDeviceManager.addDevice(device);
             }
 
+            if (enableCachedBluetoothDeviceDedup() && bondState == BluetoothDevice.BOND_BONDED) {
+                mDeviceManager.removeDuplicateInstanceForIdentityAddress(device);
+            }
+
             for (BluetoothCallback callback : mCallbacks) {
                 callback.onDeviceBondStateChanged(cachedDevice, bondState);
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index f7f0673..09b1eaf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -9,6 +9,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -24,6 +25,7 @@
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import androidx.core.graphics.drawable.IconCompat;
 
@@ -31,9 +33,12 @@
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
+import com.google.common.collect.ImmutableSet;
+
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -47,6 +52,8 @@
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+    private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
+            "com.google.android.gms.dck");
 
     private static ErrorListener sErrorListener;
 
@@ -647,4 +654,66 @@
     private static String generateExpressionWithTag(String tag, String value) {
         return getTagStart(tag) + value + getTagEnd(tag);
     }
+
+    /**
+     * Returns the BluetoothDevice's exclusive manager
+     * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
+     * given set, otherwise null.
+     */
+    @Nullable
+    private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
+        byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+        if (exclusiveManagerNameBytes == null) {
+            Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
+                    + " doesn't have exclusive manager");
+            return null;
+        }
+        String exclusiveManagerName = new String(exclusiveManagerNameBytes);
+        return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
+                : null;
+    }
+
+    /**
+     * Checks if given package is installed
+     */
+    private static boolean isPackageInstalled(Context context,
+            String packageName) {
+        PackageManager packageManager = context.getPackageManager();
+        try {
+            packageManager.getPackageInfo(packageName, 0);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.d(TAG, "Package " + packageName + " is not installed");
+        }
+        return false;
+    }
+
+    /**
+     * A BluetoothDevice is exclusively managed if
+     * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
+     * 2) the exclusive manager app name is in the allowlist.
+     * 3) the exclusive manager app is installed.
+     */
+    public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
+            @NonNull BluetoothDevice bluetoothDevice) {
+        String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+        if (exclusiveManagerName == null) {
+            return false;
+        }
+        if (!isPackageInstalled(context, exclusiveManagerName)) {
+            return false;
+        } else {
+            Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+            return true;
+        }
+    }
+
+    /**
+     * Return the allowlist for exclusive manager names.
+     */
+    @NonNull
+    public static Set<String> getExclusiveManagers() {
+        return EXCLUSIVE_MANAGERS;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 560bc46..bcdb64d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1745,12 +1745,14 @@
         final BluetoothDevice tmpDevice = mDevice;
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
+        final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
 
         // Set main device from sub device
         release();
         mDevice = newMainDevice.mDevice;
         mRssi = newMainDevice.mRssi;
         mJustDiscovered = newMainDevice.mJustDiscovered;
+        mHearingAidInfo = newMainDevice.mHearingAidInfo;
         fillData();
 
         // Set sub device from backup
@@ -1758,6 +1760,7 @@
         newMainDevice.mDevice = tmpDevice;
         newMainDevice.mRssi = tmpRssi;
         newMainDevice.mJustDiscovered = tmpJustDiscovered;
+        newMainDevice.mHearingAidInfo = tmpHearingAidInfo;
         newMainDevice.fillData();
 
         // Add the sub device back into mMemberDevices with correct hash
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 89fe268..32eec7e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -340,6 +340,20 @@
         }
     }
 
+    synchronized void removeDuplicateInstanceForIdentityAddress(BluetoothDevice device) {
+        String identityAddress = device.getIdentityAddress();
+        if (identityAddress == null || identityAddress.equals(device.getAddress())) {
+            return;
+        }
+        mCachedDevices.removeIf(d -> {
+            boolean shouldRemove = d.getDevice().getAddress().equals(identityAddress);
+            if (shouldRemove) {
+                Log.d(TAG, "Remove instance for identity address " + d);
+            }
+            return shouldRemove;
+        });
+    }
+
     public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
             cachedDevice, int state, int profileId) {
         if (profileId == BluetoothProfile.HEARING_AID) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 1d2f790..6ee403d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -21,6 +21,7 @@
 import android.annotation.CallbackExecutor;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudioContentMetadata;
 import android.bluetooth.BluetoothLeBroadcast;
@@ -62,6 +63,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
 
 /**
  * LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of
@@ -88,6 +90,8 @@
                         Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
             };
 
+    private final Context mContext;
+    private final CachedBluetoothDeviceManager mDeviceManager;
     private BluetoothLeBroadcast mServiceBroadcast;
     private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
     private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
@@ -256,8 +260,19 @@
     private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
             new BluetoothLeBroadcastAssistant.Callback() {
                 @Override
-                public void onSourceAdded(
-                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    if (DEBUG) {
+                        Log.d(
+                                TAG,
+                                "onSourceAdded(), sink = "
+                                        + sink
+                                        + ", reason = "
+                                        + reason
+                                        + ", sourceId = "
+                                        + sourceId);
+                    }
+                    updateFallbackActiveDeviceIfNeeded();
+                }
 
                 @Override
                 public void onSearchStarted(int reason) {}
@@ -301,6 +316,7 @@
                                         + ", sourceId = "
                                         + sourceId);
                     }
+                    updateFallbackActiveDeviceIfNeeded();
                 }
 
                 @Override
@@ -348,7 +364,9 @@
         }
     }
 
-    LocalBluetoothLeBroadcast(Context context) {
+    LocalBluetoothLeBroadcast(Context context, CachedBluetoothDeviceManager deviceManager) {
+        mContext = context;
+        mDeviceManager = deviceManager;
         mExecutor = Executors.newSingleThreadExecutor();
         mBuilder = new BluetoothLeAudioContentMetadata.Builder();
         mContentResolver = context.getContentResolver();
@@ -430,49 +448,6 @@
         mServiceBroadcast.startBroadcast(settings);
     }
 
-    /**
-     * Start the private Broadcast for personal audio sharing or qr code sharing.
-     *
-     * <p>The broadcast will use random string for both broadcast name and subgroup program info;
-     * The broadcast will use random string for broadcast code; The broadcast will only have one
-     * subgroup due to system limitation; The subgroup language will be null.
-     *
-     * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
-     * {@link BluetoothLeBroadcast.Callback}.
-     */
-    public void startPrivateBroadcast(int quality) {
-        mNewAppSourceName = "Sharing audio";
-        if (mServiceBroadcast == null) {
-            Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast.");
-            return;
-        }
-        if (mServiceBroadcast.getAllBroadcastMetadata().size()
-                >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) {
-            Log.d(TAG, "Skip starting the broadcast due to number limit.");
-            return;
-        }
-        String programInfo = getProgramInfo();
-        if (DEBUG) {
-            Log.d(TAG, "startBroadcast: language = null ,programInfo = " + programInfo);
-        }
-        // Current broadcast framework only support one subgroup
-        BluetoothLeBroadcastSubgroupSettings subgroupSettings =
-                buildBroadcastSubgroupSettings(
-                        /* language= */ null,
-                        programInfo,
-                        /* improveCompatibility= */
-                        BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD == quality);
-        BluetoothLeBroadcastSettings settings =
-                buildBroadcastSettings(
-                        true, // TODO: set to false after framework fix
-                        TextUtils.isEmpty(programInfo) ? null : programInfo,
-                        (mBroadcastCode != null && mBroadcastCode.length > 0)
-                                ? mBroadcastCode
-                                : null,
-                        ImmutableList.of(subgroupSettings));
-        mServiceBroadcast.startBroadcast(settings);
-    }
-
     private BluetoothLeBroadcastSettings buildBroadcastSettings(
             boolean isPublic,
             @Nullable String broadcastName,
@@ -1027,4 +1002,80 @@
             }
         }
     }
+
+    /** Update fallback active device if needed. */
+    public void updateFallbackActiveDeviceIfNeeded() {
+        if (!isEnabled(null)) {
+            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no ongoing broadcast");
+            return;
+        }
+        if (mServiceBroadcastAssistant == null) {
+            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
+            return;
+        }
+        List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
+        List<BluetoothDevice> devicesInSharing =
+                connectedDevices.stream()
+                        .filter(
+                                bluetoothDevice -> {
+                                    List<BluetoothLeBroadcastReceiveState> sourceList =
+                                            mServiceBroadcastAssistant.getAllSources(
+                                                    bluetoothDevice);
+                                    return !sourceList.isEmpty();
+                                })
+                        .collect(Collectors.toList());
+        if (devicesInSharing.isEmpty()) {
+            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
+            return;
+        }
+        List<BluetoothDevice> devices =
+                BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
+        BluetoothDevice targetDevice = null;
+        // Find the earliest connected device in sharing session.
+        int targetDeviceIdx = -1;
+        for (BluetoothDevice device : devicesInSharing) {
+            if (devices.contains(device)) {
+                int idx = devices.indexOf(device);
+                if (idx > targetDeviceIdx) {
+                    targetDeviceIdx = idx;
+                    targetDevice = device;
+                }
+            }
+        }
+        if (targetDevice == null) {
+            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
+            return;
+        }
+        Log.d(
+                TAG,
+                "updateFallbackActiveDeviceIfNeeded, set active device: "
+                        + targetDevice.getAnonymizedAddress());
+        CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
+        if (targetCachedDevice == null) {
+            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
+            return;
+        }
+        int fallbackActiveGroupId = getFallbackActiveGroupId();
+        if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+            Log.d(
+                    TAG,
+                    "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
+                            + fallbackActiveGroupId);
+            return;
+        }
+        targetCachedDevice.setActive();
+    }
+
+    private boolean isDecryptedSource(BluetoothLeBroadcastReceiveState state) {
+        return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
+                && state.getBigEncryptionState()
+                        == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+    }
+
+    private int getFallbackActiveGroupId() {
+        return Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                "bluetooth_le_broadcast_fallback_active_group_id",
+                BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManagerExt.kt
new file mode 100644
index 0000000..5dc0237
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManagerExt.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.settingslib.bluetooth
+
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** Returns a [Flow] that emits a [Unit] whenever the headset audio mode changes. */
+val LocalBluetoothManager.headsetAudioModeChanges: Flow<Unit>
+    get() {
+        return callbackFlow {
+            val callback =
+                object : BluetoothCallback {
+                    override fun onAudioModeChanged() {
+                        launch { send(Unit) }
+                    }
+                }
+
+            eventManager.registerCallback(callback)
+            awaitClose { eventManager.unregisterCallback(callback) }
+        }
+    }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 119aef6..79e4c37 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -257,7 +257,7 @@
             if (DEBUG) {
                 Log.d(TAG, "Adding local LE_AUDIO_BROADCAST profile");
             }
-            mLeAudioBroadcast = new LocalBluetoothLeBroadcast(mContext);
+            mLeAudioBroadcast = new LocalBluetoothLeBroadcast(mContext, mDeviceManager);
             // no event handler for the LE boradcast.
             mProfileNameMap.put(LocalBluetoothLeBroadcast.NAME, mLeAudioBroadcast);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 15f33d2..ba9180d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -37,6 +37,7 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@
 import com.android.settingslib.R;
 import com.android.settingslib.media.flags.Flags;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -63,6 +65,17 @@
 
     private final DeviceIconUtil mDeviceIconUtil;
 
+    /** 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);
+        } else if (isTablet()) {
+            return context.getString(R.string.media_transfer_this_device_name_tablet);
+        } else {
+            return context.getString(R.string.media_transfer_this_device_name);
+        }
+    }
+
     /** Returns the device name for the given {@code routeInfo}. */
     public static String getSystemRouteNameFromType(
             @NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
@@ -80,7 +93,7 @@
                 name = context.getString(R.string.media_transfer_dock_speaker_device_name);
                 break;
             case TYPE_BUILTIN_SPEAKER:
-                name = context.getString(R.string.media_transfer_this_device_name);
+                name = getMediaTransferThisDeviceName(context);
                 break;
             case TYPE_HDMI:
                 name = context.getString(isTv ? R.string.tv_media_transfer_default :
@@ -135,6 +148,11 @@
                 && Flags.enableTvMediaOutputDialog();
     }
 
+    static boolean isTablet() {
+        return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+                .contains("tablet");
+    }
+
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
     @SuppressWarnings("NewApi")
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt
new file mode 100644
index 0000000..5bcb82d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.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.settingslib.view.accessibility.data.repository
+
+import android.view.accessibility.CaptioningManager
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+interface CaptioningRepository {
+
+    /** The system audio caption enabled state. */
+    val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+
+    /** The system audio caption UI enabled state. */
+    val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+
+    /** Sets [isSystemAudioCaptioningEnabled]. */
+    suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean)
+}
+
+class CaptioningRepositoryImpl(
+    private val captioningManager: CaptioningManager,
+    private val backgroundCoroutineContext: CoroutineContext,
+    coroutineScope: CoroutineScope,
+) : CaptioningRepository {
+
+    private val captioningChanges: SharedFlow<CaptioningChange> =
+        callbackFlow {
+                val listener = CaptioningChangeProducingListener(this)
+                captioningManager.addCaptioningChangeListener(listener)
+                awaitClose { captioningManager.removeCaptioningChangeListener(listener) }
+            }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+
+    override val isSystemAudioCaptioningEnabled: StateFlow<Boolean> =
+        captioningChanges
+            .filterIsInstance(CaptioningChange.IsSystemAudioCaptioningEnabled::class)
+            .map { it.isEnabled }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                captioningManager.isSystemAudioCaptioningEnabled
+            )
+
+    override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean> =
+        captioningChanges
+            .filterIsInstance(CaptioningChange.IsSystemUICaptioningEnabled::class)
+            .map { it.isEnabled }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                captioningManager.isSystemAudioCaptioningUiEnabled,
+            )
+
+    override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
+        withContext(backgroundCoroutineContext) {
+            captioningManager.isSystemAudioCaptioningEnabled = isEnabled
+        }
+    }
+
+    private sealed interface CaptioningChange {
+
+        data class IsSystemAudioCaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
+
+        data class IsSystemUICaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
+    }
+
+    private class CaptioningChangeProducingListener(
+        private val scope: ProducerScope<CaptioningChange>
+    ) : CaptioningManager.CaptioningChangeListener() {
+
+        override fun onSystemAudioCaptioningChanged(enabled: Boolean) {
+            emitChange(CaptioningChange.IsSystemAudioCaptioningEnabled(enabled))
+        }
+
+        override fun onSystemAudioCaptioningUiChanged(enabled: Boolean) {
+            emitChange(CaptioningChange.IsSystemUICaptioningEnabled(enabled))
+        }
+
+        private fun emitChange(change: CaptioningChange) {
+            scope.launch { scope.send(change) }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/domain/interactor/CaptioningInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/domain/interactor/CaptioningInteractor.kt
new file mode 100644
index 0000000..858c8b3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/domain/interactor/CaptioningInteractor.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.settingslib.view.accessibility.domain.interactor
+
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
+import kotlinx.coroutines.flow.StateFlow
+
+class CaptioningInteractor(private val repository: CaptioningRepository) {
+
+    val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+        get() = repository.isSystemAudioCaptioningEnabled
+
+    val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+        get() = repository.isSystemAudioCaptioningUiEnabled
+
+    suspend fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) =
+        repository.setIsSystemAudioCaptioningEnabled(enabled)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3355fb3..6761aa7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,29 +16,71 @@
 
 package com.android.settingslib.volume.data.repository
 
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioDeviceInfo
 import android.media.AudioManager
+import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
-/** Provides audio managing functionality and data. */
+/** Provides audio streams state and managing functionality. */
 interface AudioRepository {
 
     /** Current [AudioManager.getMode]. */
     val mode: StateFlow<Int>
+
+    /**
+     * Ringtone mode.
+     *
+     * @see AudioManager.getRingerModeInternal
+     */
+    val ringerMode: StateFlow<RingerMode>
+
+    /**
+     * Communication device. Emits null when there is no communication device available.
+     *
+     * @see AudioDeviceInfo.getType
+     */
+    val communicationDevice: StateFlow<AudioDeviceInfo?>
+
+    /** State of the [AudioStream]. */
+    suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+
+    /** Current state of the [AudioStream]. */
+    suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+
+    suspend fun setVolume(audioStream: AudioStream, volume: Int)
+
+    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
 }
 
 class AudioRepositoryImpl(
+    private val context: Context,
     private val audioManager: AudioManager,
-    backgroundCoroutineContext: CoroutineContext,
-    coroutineScope: CoroutineScope,
+    private val backgroundCoroutineContext: CoroutineContext,
+    private val coroutineScope: CoroutineScope,
 ) : AudioRepository {
 
     override val mode: StateFlow<Int> =
@@ -50,4 +92,117 @@
             }
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+
+    private val audioManagerIntents: SharedFlow<String> =
+        callbackFlow {
+                val receiver =
+                    object : BroadcastReceiver() {
+                        override fun onReceive(context: Context?, intent: Intent) {
+                            intent.action?.let { action -> launch { send(action) } }
+                        }
+                    }
+                context.registerReceiver(
+                    receiver,
+                    IntentFilter().apply {
+                        for (action in allActions) {
+                            addAction(action)
+                        }
+                    }
+                )
+
+                awaitClose { context.unregisterReceiver(receiver) }
+            }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+    override val ringerMode: StateFlow<RingerMode> =
+        audioManagerIntents
+            .filter { ringerActions.contains(it) }
+            .map { RingerMode(audioManager.ringerModeInternal) }
+            .flowOn(backgroundCoroutineContext)
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                RingerMode(audioManager.ringerModeInternal),
+            )
+
+    override val communicationDevice: StateFlow<AudioDeviceInfo?>
+        get() =
+            callbackFlow {
+                    val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+                    audioManager.addOnCommunicationDeviceChangedListener(
+                        DirectExecutor.INSTANCE,
+                        listener
+                    )
+
+                    awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+                }
+                .filterNotNull()
+                .map { audioManager.communicationDevice }
+                .flowOn(backgroundCoroutineContext)
+                .stateIn(
+                    coroutineScope,
+                    SharingStarted.WhileSubscribed(),
+                    audioManager.communicationDevice,
+                )
+
+    override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+        return audioManagerIntents
+            .filter { modelActions.contains(it) }
+            .map { getCurrentAudioStream(audioStream) }
+            .flowOn(backgroundCoroutineContext)
+    }
+
+    override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+        return withContext(backgroundCoroutineContext) {
+            AudioStreamModel(
+                audioStream = audioStream,
+                minVolume = getMinVolume(audioStream),
+                maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+                volume = audioManager.getStreamVolume(audioStream.value),
+                isAffectedByRingerMode =
+                    audioManager.isStreamAffectedByRingerMode(audioStream.value),
+                isMuted = audioManager.isStreamMute(audioStream.value)
+            )
+        }
+    }
+
+    override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+        withContext(backgroundCoroutineContext) {
+            audioManager.setStreamVolume(audioStream.value, volume, 0)
+        }
+
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+        withContext(backgroundCoroutineContext) {
+            if (isMuted) {
+                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
+            } else {
+                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
+            }
+        }
+
+    private fun getMinVolume(stream: AudioStream): Int =
+        try {
+            audioManager.getStreamMinVolume(stream.value)
+        } catch (e: IllegalArgumentException) {
+            // Fallback to STREAM_VOICE_CALL because
+            // CallVolumePreferenceController.java default
+            // return STREAM_VOICE_CALL in getAudioStream
+            audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
+        }
+
+    private companion object {
+        val modelActions =
+            setOf(
+                AudioManager.STREAM_MUTE_CHANGED_ACTION,
+                AudioManager.MASTER_MUTE_CHANGED_ACTION,
+                AudioManager.VOLUME_CHANGED_ACTION,
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+            )
+        val ringerActions =
+            setOf(
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+            )
+        val allActions = ringerActions + modelActions
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
new file mode 100644
index 0000000..2b12936
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.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.settingslib.volume.data.repository
+
+import android.content.Context
+import android.media.AudioSystem
+
+/** Provides the current state of the audio system. */
+interface AudioSystemRepository {
+
+    val isSingleVolume: Boolean
+}
+
+class AudioSystemRepositoryImpl(private val context: Context) : AudioSystemRepository {
+
+    override val isSingleVolume: Boolean
+        get() = AudioSystem.isSingleVolume(context)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
new file mode 100644
index 0000000..1597b77
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.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.settingslib.volume.data.repository
+
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository providing data about connected media devices. */
+interface LocalMediaRepository {
+
+    /** Available devices list */
+    val mediaDevices: StateFlow<Collection<MediaDevice>>
+
+    /** Currently connected media device */
+    val currentConnectedDevice: StateFlow<MediaDevice?>
+}
+
+class LocalMediaRepositoryImpl(
+    private val localMediaManager: LocalMediaManager,
+    coroutineScope: CoroutineScope,
+    backgroundContext: CoroutineContext,
+) : LocalMediaRepository {
+
+    private val deviceUpdates: Flow<DevicesUpdate> = callbackFlow {
+        val callback =
+            object : LocalMediaManager.DeviceCallback {
+                override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
+                    trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
+                }
+
+                override fun onSelectedDeviceStateChanged(
+                    device: MediaDevice?,
+                    state: Int,
+                ) {
+                    trySend(DevicesUpdate.SelectedDeviceStateChanged)
+                }
+
+                override fun onDeviceAttributesChanged() {
+                    trySend(DevicesUpdate.DeviceAttributesChanged)
+                }
+            }
+        localMediaManager.registerCallback(callback)
+        localMediaManager.startScan()
+
+        awaitClose {
+            localMediaManager.stopScan()
+            localMediaManager.unregisterCallback(callback)
+        }
+    }
+
+    override val mediaDevices: StateFlow<Collection<MediaDevice>> =
+        deviceUpdates
+            .mapNotNull {
+                if (it is DevicesUpdate.DeviceListUpdate) {
+                    it.newDevices ?: emptyList()
+                } else {
+                    null
+                }
+            }
+            .flowOn(backgroundContext)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+    override val currentConnectedDevice: StateFlow<MediaDevice?> =
+        deviceUpdates
+            .map { localMediaManager.currentConnectedDevice }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                localMediaManager.currentConnectedDevice
+            )
+
+    private sealed interface DevicesUpdate {
+
+        data class DeviceListUpdate(val newDevices: List<MediaDevice>?) : DevicesUpdate
+
+        data object SelectedDeviceStateChanged : DevicesUpdate
+
+        data object DeviceAttributesChanged : DevicesUpdate
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
new file mode 100644
index 0000000..93aa90d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioManager
+import android.media.session.MediaController
+import android.media.session.MediaSessionManager
+import android.media.session.PlaybackState
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.headsetAudioModeChanges
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides controllers for currently active device media sessions. */
+interface MediaControllerRepository {
+
+    /** Current [MediaController]. Null is emitted when there is no active [MediaController]. */
+    val activeMediaController: StateFlow<MediaController?>
+}
+
+class MediaControllerRepositoryImpl(
+    private val context: Context,
+    private val mediaSessionManager: MediaSessionManager,
+    localBluetoothManager: LocalBluetoothManager?,
+    coroutineScope: CoroutineScope,
+    backgroundContext: CoroutineContext,
+) : MediaControllerRepository {
+
+    private val devicesChanges: Flow<Unit> =
+        callbackFlow {
+                val receiver =
+                    object : BroadcastReceiver() {
+                        override fun onReceive(context: Context?, intent: Intent?) {
+                            if (AudioManager.STREAM_DEVICES_CHANGED_ACTION == intent?.action) {
+                                launch { send(Unit) }
+                            }
+                        }
+                    }
+                context.registerReceiver(
+                    receiver,
+                    IntentFilter(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
+                )
+
+                awaitClose { context.unregisterReceiver(receiver) }
+            }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+
+    override val activeMediaController: StateFlow<MediaController?> =
+        combine(
+                localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
+                    ?: emptyFlow(),
+                devicesChanges.onStart { emit(Unit) },
+            ) { _, _ ->
+                getActiveLocalMediaController()
+            }
+            .flowOn(backgroundContext)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+
+    private fun getActiveLocalMediaController(): MediaController? {
+        var localController: MediaController? = null
+        val remoteMediaSessionLists: MutableList<String> = ArrayList()
+        for (controller in mediaSessionManager.getActiveSessions(null)) {
+            val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+            val playbackState = controller.playbackState ?: continue
+            if (inactivePlaybackStates.contains(playbackState.state)) {
+                continue
+            }
+            when (playbackInfo.playbackType) {
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+                    if (localController?.packageName.equals(controller.packageName)) {
+                        localController = null
+                    }
+                    if (!remoteMediaSessionLists.contains(controller.packageName)) {
+                        remoteMediaSessionLists.add(controller.packageName)
+                    }
+                }
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+                    if (
+                        localController == null &&
+                            !remoteMediaSessionLists.contains(controller.packageName)
+                    ) {
+                        localController = controller
+                    }
+                }
+            }
+        }
+        return localController
+    }
+
+    private companion object {
+        val inactivePlaybackStates =
+            setOf(PlaybackState.STATE_STOPPED, PlaybackState.STATE_NONE, PlaybackState.STATE_ERROR)
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
new file mode 100644
index 0000000..58f3c2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.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.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] audio stream. */
+@JvmInline
+value class AudioStream(val value: Int) {
+    init {
+        require(value in supportedStreamTypes) { "Unsupported stream=$value" }
+    }
+
+    private companion object {
+        val supportedStreamTypes =
+            setOf(
+                AudioManager.STREAM_VOICE_CALL,
+                AudioManager.STREAM_SYSTEM,
+                AudioManager.STREAM_RING,
+                AudioManager.STREAM_MUSIC,
+                AudioManager.STREAM_ALARM,
+                AudioManager.STREAM_NOTIFICATION,
+                AudioManager.STREAM_BLUETOOTH_SCO,
+                AudioManager.STREAM_SYSTEM_ENFORCED,
+                AudioManager.STREAM_DTMF,
+                AudioManager.STREAM_TTS,
+                AudioManager.STREAM_ACCESSIBILITY,
+                AudioManager.STREAM_ASSISTANT,
+            )
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
new file mode 100644
index 0000000..c1be1ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.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.settingslib.volume.shared.model
+
+/** Current state of the audio stream. */
+data class AudioStreamModel(
+    val audioStream: AudioStream,
+    val volume: Int,
+    val minVolume: Int,
+    val maxVolume: Int,
+    val isAffectedByRingerMode: Boolean,
+    val isMuted: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
new file mode 100644
index 0000000..9f03927
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] ringer mode. */
+@JvmInline
+value class RingerMode(val value: Int) {
+
+    init {
+        require(value in supportedRingerModes) { "Unsupported stream=$value" }
+    }
+
+    private companion object {
+        val supportedRingerModes =
+            setOf(
+                AudioManager.RINGER_MODE_SILENT,
+                AudioManager.RINGER_MODE_VIBRATE,
+                AudioManager.RINGER_MODE_NORMAL,
+                AudioManager.RINGER_MODE_MAX,
+            )
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7ba..f303ab5 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@
         "android.test.runner",
         "telephony-common",
         "android.test.base",
+        "android.test.mock",
+        "truth",
     ],
 
     platform_apis: true,
@@ -49,16 +51,23 @@
         "androidx.test.core",
         "androidx.test.rules",
         "androidx.test.espresso.core",
+        "androidx.test.ext.junit",
         "flag-junit",
-        "mockito-target-minus-junit4",
+        "kotlinx_coroutines_test",
+        "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "truth",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibSettingsSpinner",
         "SettingsLibUsageProgressBarPreference",
         "settingslib_media_flags_lib",
-        "kotlinx_coroutines_test",
     ],
 
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
     dxflags: ["--multi-dex"],
+    manifest: "AndroidManifest.xml",
 }
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca..9fb1c1f 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
-    <application>
+    <application android:debuggable="true" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
         <activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
 
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee88..9de6019 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Runs Tests for SettingsLib.">
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="SettingsLibTests.apk" />
+        <option name="install-arg" value="-t" />
     </target_preparer>
 
     <option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepositoryTest.kt
new file mode 100644
index 0000000..a5233e7
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepositoryTest.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.settingslib.view.accessibility.data.repository
+
+import android.view.accessibility.CaptioningManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class CaptioningRepositoryTest {
+
+    @Captor
+    private lateinit var listenerCaptor: ArgumentCaptor<CaptioningManager.CaptioningChangeListener>
+
+    @Mock private lateinit var captioningManager: CaptioningManager
+
+    private lateinit var underTest: CaptioningRepository
+
+    private val testScope = TestScope()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            CaptioningRepositoryImpl(
+                captioningManager,
+                testScope.testScheduler,
+                testScope.backgroundScope
+            )
+    }
+
+    @Test
+    fun isSystemAudioCaptioningEnabled_change_repositoryEmits() {
+        testScope.runTest {
+            `when`(captioningManager.isEnabled).thenReturn(false)
+            val isSystemAudioCaptioningEnabled = mutableListOf<Boolean>()
+            underTest.isSystemAudioCaptioningEnabled
+                .onEach { isSystemAudioCaptioningEnabled.add(it) }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            triggerOnSystemAudioCaptioningChange()
+            runCurrent()
+
+            assertThat(isSystemAudioCaptioningEnabled)
+                .containsExactlyElementsIn(listOf(false, true))
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun isSystemAudioCaptioningUiEnabled_change_repositoryEmits() {
+        testScope.runTest {
+            `when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(false)
+            val isSystemAudioCaptioningUiEnabled = mutableListOf<Boolean>()
+            underTest.isSystemAudioCaptioningUiEnabled
+                .onEach { isSystemAudioCaptioningUiEnabled.add(it) }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            triggerSystemAudioCaptioningUiChange()
+            runCurrent()
+
+            assertThat(isSystemAudioCaptioningUiEnabled)
+                .containsExactlyElementsIn(listOf(false, true))
+                .inOrder()
+        }
+    }
+
+    private fun triggerSystemAudioCaptioningUiChange(enabled: Boolean = true) {
+        verify(captioningManager).addCaptioningChangeListener(listenerCaptor.capture())
+        listenerCaptor.value.onSystemAudioCaptioningUiChanged(enabled)
+    }
+
+    private fun triggerOnSystemAudioCaptioningChange(enabled: Boolean = true) {
+        verify(captioningManager).addCaptioningChangeListener(listenerCaptor.capture())
+        listenerCaptor.value.onSystemAudioCaptioningChanged(enabled)
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
new file mode 100644
index 0000000..fd253c6
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.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.settingslib.view.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCaptioningRepository : CaptioningRepository {
+
+    private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
+    override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+        get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
+
+    private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
+    override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+        get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
+
+    override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
+        mutableIsSystemAudioCaptioningEnabled.value = isEnabled
+    }
+
+    fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
+        mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 0000000..7b70c64
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+    @Captor
+    private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+    @Captor
+    private lateinit var communicationDeviceListenerCaptor:
+        ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var audioManager: AudioManager
+    @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+    private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+    private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+    private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+    private val testScope = TestScope()
+
+    private lateinit var underTest: AudioRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+        `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+        `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+        `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+        `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+        `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+            volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+            triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+        }
+        `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+            isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+            triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+        }
+        `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+            volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+        }
+        `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+            isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+        }
+        `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+            isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+        }
+
+        underTest =
+            AudioRepositoryImpl(
+                context,
+                audioManager,
+                testScope.testScheduler,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun audioModeChanges_repositoryEmits() {
+        testScope.runTest {
+            val modes = mutableListOf<Int>()
+            underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+
+            triggerModeChange(AudioManager.MODE_IN_CALL)
+            runCurrent()
+
+            assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+        }
+    }
+
+    @Test
+    fun ringerModeChanges_repositoryEmits() {
+        testScope.runTest {
+            val modes = mutableListOf<RingerMode>()
+            underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+
+            `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+            triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+            runCurrent()
+
+            assertThat(modes)
+                .containsExactly(
+                    RingerMode(AudioManager.RINGER_MODE_NORMAL),
+                    RingerMode(AudioManager.RINGER_MODE_SILENT),
+                )
+        }
+    }
+
+    @Test
+    fun communicationDeviceChanges_repositoryEmits() {
+        testScope.runTest {
+            var device: AudioDeviceInfo? = null
+            underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+            runCurrent()
+
+            triggerConnectedDeviceChange(communicationDevice)
+            runCurrent()
+
+            assertThat(device).isSameInstanceAs(communicationDevice)
+        }
+    }
+
+    @Test
+    fun adjustingVolume_changesTheStream() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setVolume(audioStream, 50)
+            runCurrent()
+
+            assertThat(streamModel)
+                .isEqualTo(
+                    AudioStreamModel(
+                        audioStream = audioStream,
+                        volume = 50,
+                        minVolume = MIN_VOLUME,
+                        maxVolume = MAX_VOLUME,
+                        isAffectedByRingerMode = false,
+                        isMuted = false,
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun adjustingVolume_currentModeIsUpToDate() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setVolume(audioStream, 50)
+            runCurrent()
+
+            assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+        }
+    }
+
+    @Test
+    fun muteStream_mutesTheStream() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setMuted(audioStream, true)
+            runCurrent()
+
+            assertThat(streamModel)
+                .isEqualTo(
+                    AudioStreamModel(
+                        audioStream = audioStream,
+                        volume = 0,
+                        minVolume = MIN_VOLUME,
+                        maxVolume = MAX_VOLUME,
+                        isAffectedByRingerMode = false,
+                        isMuted = true,
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun unmuteStream_unmutesTheStream() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            isMuteByStream[audioStream.value] = true
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setMuted(audioStream, false)
+            runCurrent()
+
+            assertThat(streamModel)
+                .isEqualTo(
+                    AudioStreamModel(
+                        audioStream = audioStream,
+                        volume = 0,
+                        minVolume = MIN_VOLUME,
+                        maxVolume = MAX_VOLUME,
+                        isAffectedByRingerMode = false,
+                        isMuted = false,
+                    )
+                )
+        }
+    }
+
+    private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+        verify(audioManager)
+            .addOnCommunicationDeviceChangedListener(
+                any(),
+                communicationDeviceListenerCaptor.capture(),
+            )
+        communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+    }
+
+    private fun triggerModeChange(mode: Int) {
+        verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+        modeListenerCaptor.value.onModeChanged(mode)
+    }
+
+    private fun triggerIntent(action: String) {
+        verify(context).registerReceiver(receiverCaptor.capture(), any())
+        receiverCaptor.value.onReceive(context, Intent(action))
+    }
+
+    private companion object {
+        const val MIN_VOLUME = 0
+        const val MAX_VOLUME = 100
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362f..dddf8e82 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
 
 package com.android.settingslib.volume.data.repository
 
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
 
 class FakeAudioRepository : AudioRepository {
 
@@ -26,7 +32,59 @@
     override val mode: StateFlow<Int>
         get() = mutableMode.asStateFlow()
 
+    private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+    override val ringerMode: StateFlow<RingerMode>
+        get() = mutableRingerMode.asStateFlow()
+
+    private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+    override val communicationDevice: StateFlow<AudioDeviceInfo?>
+        get() = mutableCommunicationDevice.asStateFlow()
+
+    private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+    private fun getAudioStreamModelState(
+        audioStream: AudioStream
+    ): MutableStateFlow<AudioStreamModel> =
+        models.getOrPut(audioStream) {
+            MutableStateFlow(
+                AudioStreamModel(
+                    audioStream = audioStream,
+                    volume = 0,
+                    minVolume = 0,
+                    maxVolume = 0,
+                    isAffectedByRingerMode = false,
+                    isMuted = false,
+                )
+            )
+        }
+
+    override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+        getAudioStreamModelState(audioStream).asStateFlow()
+
+    override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+        getAudioStreamModelState(audioStream).value
+
+    override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+        getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+    }
+
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+        getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+    }
+
     fun setMode(newMode: Int) {
         mutableMode.value = newMode
     }
+
+    fun setRingerMode(newRingerMode: RingerMode) {
+        mutableRingerMode.value = newRingerMode
+    }
+
+    fun setCommunicationDevice(device: AudioDeviceInfo?) {
+        mutableCommunicationDevice.value = device
+    }
+
+    fun setAudioStreamModel(model: AudioStreamModel) {
+        getAudioStreamModelState(model.audioStream).update { model }
+    }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
new file mode 100644
index 0000000..d106bce
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.android.settingslib.volume.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class LocalMediaRepositoryImplTest {
+
+    @Mock private lateinit var localMediaManager: LocalMediaManager
+    @Mock private lateinit var mediaDevice1: MediaDevice
+    @Mock private lateinit var mediaDevice2: MediaDevice
+
+    @Captor
+    private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
+
+    private val testScope = TestScope()
+
+    private lateinit var underTest: LocalMediaRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            LocalMediaRepositoryImpl(
+                localMediaManager,
+                testScope.backgroundScope,
+                testScope.testScheduler,
+            )
+    }
+
+    @Test
+    fun mediaDevices_areUpdated() {
+        testScope.runTest {
+            var mediaDevices: Collection<MediaDevice>? = null
+            underTest.mediaDevices.onEach { mediaDevices = it }.launchIn(backgroundScope)
+            runCurrent()
+            verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
+            deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
+            runCurrent()
+
+            assertThat(mediaDevices).hasSize(2)
+            assertThat(mediaDevices).contains(mediaDevice1)
+            assertThat(mediaDevices).contains(mediaDevice2)
+        }
+    }
+
+    @Test
+    fun deviceListUpdated_currentConnectedDeviceUpdated() {
+        testScope.runTest {
+            var currentConnectedDevice: MediaDevice? = null
+            underTest.currentConnectedDevice
+                .onEach { currentConnectedDevice = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            `when`(localMediaManager.currentConnectedDevice).thenReturn(mediaDevice1)
+            verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
+            deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
+            runCurrent()
+
+            assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
new file mode 100644
index 0000000..f07b1bff
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioManager
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSessionManager
+import android.media.session.PlaybackState
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MediaControllerRepositoryImplTest {
+
+    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var mediaSessionManager: MediaSessionManager
+    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var eventManager: BluetoothEventManager
+
+    @Mock private lateinit var stoppedMediaController: MediaController
+    @Mock private lateinit var statelessMediaController: MediaController
+    @Mock private lateinit var errorMediaController: MediaController
+    @Mock private lateinit var remoteMediaController: MediaController
+    @Mock private lateinit var localMediaController: MediaController
+
+    @Mock private lateinit var remotePlaybackInfo: PlaybackInfo
+    @Mock private lateinit var localPlaybackInfo: PlaybackInfo
+
+    private val testScope = TestScope()
+
+    private lateinit var underTest: MediaControllerRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(localBluetoothManager.eventManager).thenReturn(eventManager)
+
+        `when`(stoppedMediaController.playbackState).thenReturn(stateStopped)
+        `when`(stoppedMediaController.packageName).thenReturn("test.pkg.stopped")
+        `when`(statelessMediaController.playbackState).thenReturn(stateNone)
+        `when`(statelessMediaController.packageName).thenReturn("test.pkg.stateless")
+        `when`(errorMediaController.playbackState).thenReturn(stateError)
+        `when`(errorMediaController.packageName).thenReturn("test.pkg.error")
+        `when`(remoteMediaController.playbackState).thenReturn(statePlaying)
+        `when`(remoteMediaController.playbackInfo).thenReturn(remotePlaybackInfo)
+        `when`(remoteMediaController.packageName).thenReturn("test.pkg.remote")
+        `when`(localMediaController.playbackState).thenReturn(statePlaying)
+        `when`(localMediaController.playbackInfo).thenReturn(localPlaybackInfo)
+        `when`(localMediaController.packageName).thenReturn("test.pkg.local")
+
+        `when`(remotePlaybackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        `when`(localPlaybackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+
+        underTest =
+            MediaControllerRepositoryImpl(
+                context,
+                mediaSessionManager,
+                localBluetoothManager,
+                testScope.backgroundScope,
+                testScope.testScheduler,
+            )
+    }
+
+    @Test
+    fun playingMediaDevicesAvailable_sessionIsActive() {
+        testScope.runTest {
+            `when`(mediaSessionManager.getActiveSessions(any()))
+                .thenReturn(
+                    listOf(
+                        stoppedMediaController,
+                        statelessMediaController,
+                        errorMediaController,
+                        remoteMediaController,
+                        localMediaController
+                    )
+                )
+            var mediaController: MediaController? = null
+            underTest.activeMediaController
+                .onEach { mediaController = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            triggerDevicesChange()
+            triggerOnAudioModeChanged()
+            runCurrent()
+
+            assertThat(mediaController).isSameInstanceAs(localMediaController)
+        }
+    }
+
+    @Test
+    fun noPlayingMediaDevicesAvailable_sessionIsInactive() {
+        testScope.runTest {
+            `when`(mediaSessionManager.getActiveSessions(any()))
+                .thenReturn(
+                    listOf(
+                        stoppedMediaController,
+                        statelessMediaController,
+                        errorMediaController,
+                    )
+                )
+            var mediaController: MediaController? = null
+            underTest.activeMediaController
+                .onEach { mediaController = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            triggerDevicesChange()
+            triggerOnAudioModeChanged()
+            runCurrent()
+
+            assertThat(mediaController).isNull()
+        }
+    }
+
+    private fun triggerDevicesChange() {
+        verify(context).registerReceiver(receiverCaptor.capture(), any())
+        receiverCaptor.value.onReceive(context, Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
+    }
+
+    private fun triggerOnAudioModeChanged() {
+        verify(eventManager).registerCallback(callbackCaptor.capture())
+        callbackCaptor.value.onAudioModeChanged()
+    }
+
+    private companion object {
+        val statePlaying =
+            PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
+        val stateError = PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
+        val stateStopped =
+            PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0, 0f).build()
+        val stateNone = PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc..4dbf865 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
 package com.android.settingslib.volume.domain.interactor
 
 import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
 import com.android.settingslib.volume.data.repository.FakeAudioRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
 
     private val testScope = TestScope()
     private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index a88a9c7..2d07e5d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -16,6 +16,9 @@
 package com.android.settingslib;
 
 import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
+import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
+import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,10 +31,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.hardware.usb.flags.Flags;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.os.BatteryManager;
@@ -59,6 +62,7 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowSettings;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -72,21 +76,16 @@
     private static final String PERCENTAGE_49 = "49%";
     private static final String PERCENTAGE_50 = "50%";
     private static final String PERCENTAGE_100 = "100%";
+    private static final long CURRENT_TIMESTAMP = System.currentTimeMillis();
 
     private AudioManager mAudioManager;
     private Context mContext;
-    @Mock
-    private LocationManager mLocationManager;
-    @Mock
-    private ServiceState mServiceState;
-    @Mock
-    private NetworkRegistrationInfo mNetworkRegistrationInfo;
-    @Mock
-    private UsbPort mUsbPort;
-    @Mock
-    private UsbManager mUsbManager;
-    @Mock
-    private UsbPortStatus mUsbPortStatus;
+    @Mock private LocationManager mLocationManager;
+    @Mock private ServiceState mServiceState;
+    @Mock private NetworkRegistrationInfo mNetworkRegistrationInfo;
+    @Mock private UsbPort mUsbPort;
+    @Mock private UsbManager mUsbManager;
+    @Mock private UsbPortStatus mUsbPortStatus;
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -102,27 +101,37 @@
 
     @After
     public void reset() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
     }
 
     @Test
     public void testUpdateLocationEnabled() {
         int currentUserId = ActivityManager.getCurrentUser();
-        Utils.updateLocationEnabled(mContext, true, currentUserId,
-                Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+        Utils.updateLocationEnabled(
+                mContext, true, currentUserId, Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
 
-        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_CHANGER,
-                Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
-                Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+        assertThat(
+                        Settings.Secure.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Secure.LOCATION_CHANGER,
+                                Settings.Secure.LOCATION_CHANGER_UNKNOWN))
+                .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
     }
 
     @Test
     public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
-        final String[] expectedPercentages =
-                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
-                        PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
+        final String[] expectedPercentages = {
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_1,
+            PERCENTAGE_1,
+            PERCENTAGE_49,
+            PERCENTAGE_49,
+            PERCENTAGE_50,
+            PERCENTAGE_50,
+            PERCENTAGE_100
+        };
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -132,9 +141,17 @@
 
     @Test
     public void testFormatPercentage_RoundFalse_NoRound() {
-        final String[] expectedPercentages =
-                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
-                        PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
+        final String[] expectedPercentages = {
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_49,
+            PERCENTAGE_49,
+            PERCENTAGE_49,
+            PERCENTAGE_50,
+            PERCENTAGE_100
+        };
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -146,7 +163,9 @@
     public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
         Resources resources = mock(Resources.class);
         when(resources.getInteger(
-                eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
+                        eq(
+                                com.android.internal.R.integer
+                                        .config_storageManagerDaystoRetainDefault)))
                 .thenReturn(60);
         assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
     }
@@ -214,8 +233,10 @@
     public void isInService_voiceOutOfServiceDataInService_returnTrue() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
         assertThat(Utils.isInService(mServiceState)).isTrue();
@@ -224,8 +245,10 @@
     @Test
     public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
@@ -235,8 +258,10 @@
     @Test
     public void isInService_voiceOutOfServiceDataNull_returnFalse() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(null);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(null);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
     }
@@ -244,8 +269,10 @@
     @Test
     public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
@@ -260,96 +287,106 @@
 
     @Test
     public void getCombinedServiceState_servicestateNull_returnOutOfService() {
-        assertThat(Utils.getCombinedServiceState(null)).isEqualTo(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        assertThat(Utils.getCombinedServiceState(null))
+                .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_POWER_OFF);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_POWER_OFF);
     }
 
     @Test
     public void getCombinedServiceState_voiceInService_returnInService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_IN_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_IN_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_IN_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_IN_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getDataRegistrationState()).thenReturn(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getDataRegistrationState())
+                .thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
     }
 
     @Test
     public void getBatteryStatus_statusIsFull_returnFullString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
-                BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                        .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full));
     }
 
     @Test
     public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
-                BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                        .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full_charged));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
     }
 
     @Test
     public void getBatteryStatus_batteryLevelIs100_returnFullString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_FULL);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full));
     }
 
     @Test
     public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_FULL);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full_charged));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
     }
 
     @Test
@@ -359,8 +396,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging));
     }
 
     @Test
@@ -370,8 +407,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging_dock));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging_dock));
     }
 
     @Test
@@ -381,8 +418,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging_wireless));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging_wireless));
     }
 
     @Test
@@ -392,8 +429,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging));
     }
 
     @Test
@@ -403,8 +440,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging));
     }
 
     @Test
@@ -503,12 +540,97 @@
     @Test
     public void containsIncompatibleChargers_disableWarning_returnFalse() {
         setupIncompatibleCharging();
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
 
         assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
     }
 
+    @Test
+    public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
+        updateWirelessChargingNotificationTimestamp(
+                mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
+        updateWirelessChargingNotificationTimestamp(
+                mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+        Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp())
+                .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
+        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
+        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
+        final long monthAgo = Duration.ofDays(31).toMillis();
+        final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+        updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
+        final long monthAgo = Duration.ofDays(31).toMillis();
+        final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+        updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+        Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
+        updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
+    }
+
+    @Test
+    public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
+        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp())
+                .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+        assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
+    }
+
+    @Test
+    public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
+        Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
+
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingWarningTip_disabled_returnFalse() {
+        Utils.updateWirelessChargingWarningEnabled(mContext, false, TAG);
+
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isFalse();
+    }
+
     private void setupIncompatibleCharging() {
         setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
     }
@@ -520,6 +642,13 @@
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
+    }
+
+    private long getWirelessChargingNotificationTimestamp() {
+        return Settings.Secure.getLong(
+                mContext.getContentResolver(),
+                Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+                WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
index 994c1ee..e32d880 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
@@ -24,12 +26,16 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import com.android.settingslib.Utils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -39,6 +45,8 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -60,6 +68,10 @@
     private ApplicationInfo mAppInfo;
     private ApplicationsState.AppEntry mAppEntry;
     private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+    private ShadowPackageManager mShadowPackageManager;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
@@ -70,6 +82,7 @@
         mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
         mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
         doReturn(mIcon).when(mIcon).mutate();
+        mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
     }
 
     @After
@@ -151,6 +164,32 @@
         assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
     }
 
+    @Test
+    public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
+        mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = APP_PACKAGE_NAME;
+        packageInfo.setApexPackageName("com.test.apex.package");
+        mShadowPackageManager.installPackage(packageInfo);
+
+        assertThat(
+                AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+    }
+
+    @Test
+    public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
+        mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = APP_PACKAGE_NAME;
+        packageInfo.applicationInfo = applicationInfo;
+        mShadowPackageManager.installPackage(packageInfo);
+
+        assertThat(
+                AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+    }
+
     private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
         ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
         appEntry.label = "label";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 34d8148..827d8fa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
 import static android.os.UserHandle.MU_ENABLED;
 import static android.os.UserHandle.USER_SYSTEM;
 
@@ -58,6 +59,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 
@@ -70,6 +72,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -89,6 +92,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
@@ -137,6 +141,9 @@
     @Mock
     private IPackageManager mPackageManagerService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Implements(value = IconDrawableFactory.class)
     public static class ShadowIconDrawableFactory {
 
@@ -169,7 +176,9 @@
         public List<ModuleInfo> getInstalledModules(int flags) {
             if (mInstalledModules.isEmpty()) {
                 for (String moduleName : mModuleNames) {
-                    mInstalledModules.add(createModuleInfo(moduleName));
+                    mInstalledModules.add(
+                            createModuleInfo(moduleName,
+                                    TextUtils.concat(moduleName, ".apex").toString()));
                 }
             }
             return mInstalledModules;
@@ -188,10 +197,11 @@
             return resolveInfos;
         }
 
-        private ModuleInfo createModuleInfo(String packageName) {
+        private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
             final ModuleInfo info = new ModuleInfo();
             info.setName(packageName);
             info.setPackageName(packageName);
+            info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
             // will treat any app with package name that contains "hidden" as hidden module
             info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
             return info;
@@ -822,4 +832,32 @@
         assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
                 .isEqualTo(PKG_1);
     }
+
+    @Test
+    public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
+        mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationsState.sInstance = null;
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+        String normalModulePackage = "test.module.1";
+        String hiddenModulePackage = "test.hidden.module.2";
+        String hiddenApexPackage = "test.hidden.module.2.apex";
+
+        assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+        assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+        assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
+    }
+
+    @Test
+    public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
+        mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationsState.sInstance = null;
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+        String normalModulePackage = "test.module.1";
+        String hiddenModulePackage = "test.hidden.module.2";
+        String hiddenApexPackage = "test.hidden.module.2.apex";
+
+        assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+        assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+        assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index f7ec80b..475a6d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -24,6 +26,8 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.Uri;
@@ -49,6 +53,8 @@
     private BluetoothDevice mBluetoothDevice;
     @Mock
     private AudioManager mAudioManager;
+    @Mock
+    private PackageManager mPackageManager;
 
     private Context mContext;
     private static final String STRING_METADATA = "string_metadata";
@@ -59,6 +65,7 @@
     private static final String CONTROL_METADATA =
             "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
                     + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
+    private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
 
     @Before
     public void setUp() {
@@ -372,4 +379,55 @@
         assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
                 mAudioManager)).isEqualTo(false);
     }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                null);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+            throws Exception {
+        final String exclusiveManagerName =
+                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
+                        FAKE_EXCLUSIVE_MANAGER_NAME);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                exclusiveManagerName.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
+                exclusiveManagerName, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
+            throws Exception {
+        final String exclusiveManagerName =
+                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
+                        FAKE_EXCLUSIVE_MANAGER_NAME);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                exclusiveManagerName.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(true);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 9db8b47..461ecf5d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1769,8 +1769,10 @@
     public void switchMemberDeviceContent_switchMainDevice_switchesSuccessful() {
         mCachedDevice.mRssi = RSSI_1;
         mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mSubCachedDevice.mRssi = RSSI_2;
         mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
+        mSubCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         mCachedDevice.addMemberDevice(mSubCachedDevice);
 
         mCachedDevice.switchMemberDeviceContent(mSubCachedDevice);
@@ -1778,10 +1780,12 @@
         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
+        assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
         verify(mCachedDevice).fillData();
         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
+        assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
         verify(mSubCachedDevice).fillData();
         assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index ceba9be..e2d58d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -24,6 +24,7 @@
 import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID;
 import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID;
 import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.getMediaTransferThisDeviceName;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -114,7 +115,7 @@
         when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
 
         assertThat(mPhoneMediaDevice.getName())
-                .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
+                .isEqualTo(getMediaTransferThisDeviceName(mContext));
     }
 
     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1e146a5..e4a762a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1113,6 +1113,10 @@
         dumpSetting(s, p,
                 Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
                 GlobalSettingsProto.Notification.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
+        dumpSetting(s, p,
+                Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+                GlobalSettingsProto.Notification
+                        .DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS);
         p.end(notificationToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 6f3c88f..1c9e748 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -612,12 +612,19 @@
             String packageName) {
         List<String> changedKeys = new ArrayList<>();
         final Iterator<Map.Entry<String, Setting>> iterator = mSettings.entrySet().iterator();
+        int index = prefix.lastIndexOf('/');
+        String namespace = index < 0 ? "" : prefix.substring(0, index);
+        Map<String, String> trunkFlagMap = (mNamespaceDefaults == null)
+                ? null : mNamespaceDefaults.get(namespace);
         // Delete old keys with the prefix that are not part of the new set.
+        // trunk flags will not be configured with restricted propagation
+        // trunk flags will be explicitly set, so not removing them here
         while (iterator.hasNext()) {
             Map.Entry<String, Setting> entry = iterator.next();
             final String key = entry.getKey();
             final Setting oldState = entry.getValue();
-            if (key != null && key.startsWith(prefix) && !keyValues.containsKey(key)) {
+            if (key != null && (trunkFlagMap == null || !trunkFlagMap.containsKey(key))
+                    && key.startsWith(prefix) && !keyValues.containsKey(key)) {
                 iterator.remove();
 
                 FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index edbc0b3..ecac5ee 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,11 +14,3 @@
     bug: "311155098"
     is_fixed_read_only: true
 }
-
-flag {
-  name: "configurable_font_scale_default"
-  namespace: "large_screen_experiences_app_compat"
-  description: "Whether the font_scale is read from a device dependent configuration file"
-  bug: "319808237"
-  is_fixed_read_only: true
-}
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 16de478..b58187d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -185,6 +185,7 @@
                     Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DEVICE_IDLE_CONSTANTS,
+                    Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
                     Settings.Global.DISABLE_WINDOW_BLURS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
                     Settings.Global.BATTERY_TIP_CONSTANTS,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 95e0e1b..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -908,6 +908,12 @@
     <!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
     <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
 
+    <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+    <uses-permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+
+    <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
+    <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 900a2f8..d1a3571 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0050676..f877d7a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -475,6 +475,9 @@
         <service android:name=".screenrecord.RecordingService"
                  android:foregroundServiceType="systemExempted"/>
 
+        <service android:name=".recordissue.IssueRecordingService"
+                 android:foregroundServiceType="systemExempted"/>
+
         <receiver android:name=".SysuiRestartReceiver"
             android:exported="false">
             <intent-filter>
@@ -992,7 +995,6 @@
             android:theme="@style/Theme.EditWidgetsActivity"
             android:excludeFromRecents="true"
             android:autoRemoveFromRecents="true"
-            android:showOnLockScreen="true"
             android:launchMode="singleTop"
             android:exported="false">
         </activity>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 0df9bac..d674b6c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 3fc351c..64dcf6e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3db99f28..2ad7192 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -67,6 +67,16 @@
 }
 
 flag {
+    name: "nssl_falsing_fix"
+    namespace: "systemui"
+    description: "Minor touch changes to prevent falsing errors in NSSL"
+    bug: "316551193"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "refactor_get_current_user"
     namespace: "systemui"
     description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
@@ -94,7 +104,7 @@
         " standard background color is desired.  This was the behavior before we discovered"
         " a resources threading issue, which we worked around by tinting the notification"
         " backgrounds and footer buttons."
-    bug: "294347738"
+    bug: "294830092"
 }
 
 flag {
@@ -158,6 +168,9 @@
         "Note that, even after this callback is called, we're waiting for all windows to finish "
         " drawing."
     bug: "295873557"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -323,13 +336,6 @@
 }
 
 flag {
-    name: "screenshare_notification_hiding"
-    namespace: "systemui"
-    description: "Enable hiding of notifications during screenshare"
-    bug: "312784809"
-}
-
-flag {
    name: "run_fingerprint_detect_on_dismissible_keyguard"
    namespace: "systemui"
    description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -365,9 +371,9 @@
 }
 
 flag {
-   name: "enable_keyguard_compose"
+   name: "compose_lockscreen"
    namespace: "systemui"
-   description: "Enables the compose version of keyguard."
+   description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
    bug: "301968149"
 }
 
@@ -384,3 +390,18 @@
    description: "Enables on-screen contextual tip about how to take screenshot."
    bug: "322891421"
 }
+
+flag {
+   name: "enable_contextual_tips"
+   description: "Enables showing contextual tips."
+   namespace: "systemui"
+   bug: "322891421"
+}
+
+flag {
+   name: "shaderlib_loading_effect_refactor"
+   namespace: "systemui"
+   description: "Extend shader library to provide the common loading effects."
+   bug: "282007590"
+}
+
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 872187a..c1125f0 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index efdbfdb..055252b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -353,7 +353,7 @@
 
         /**
          * Return the first [GradientDrawable] found in [drawable], or null if none is found. If
-         * [drawable] is a [LayerDrawable], this will return the first layer that is a
+         * [drawable] is a [LayerDrawable], this will return the first layer that has a
          * [GradientDrawable].
          */
         fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
@@ -367,8 +367,8 @@
 
             if (drawable is LayerDrawable) {
                 for (i in 0 until drawable.numberOfLayers) {
-                    val maybeGradient = drawable.getDrawable(i)
-                    if (maybeGradient is GradientDrawable) {
+                    val maybeGradient = findGradientDrawable(drawable.getDrawable(i))
+                    if (maybeGradient != null) {
                         return maybeGradient
                     }
                 }
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index d3f66e3..4cbc18c 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // 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"
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index 482776a..6fc13d7 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 9a4347d..4f7a43e 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt
new file mode 100644
index 0000000..846abf7e
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.compose.ui.graphics.painter
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ */
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+public class DrawablePainter(public val drawable: Drawable) : Painter(), RememberObserver {
+    private var drawInvalidateTick by mutableStateOf(0)
+    private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+    private val callback: Drawable.Callback by lazy {
+        object : Drawable.Callback {
+            override fun invalidateDrawable(d: Drawable) {
+                // Update the tick so that we get re-drawn
+                drawInvalidateTick++
+                // Update our intrinsic size too
+                drawableIntrinsicSize = drawable.intrinsicSize
+            }
+
+            override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+                MAIN_HANDLER.postAtTime(what, time)
+            }
+
+            override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+                MAIN_HANDLER.removeCallbacks(what)
+            }
+        }
+    }
+
+    init {
+        if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+            // Update the drawable's bounds to match the intrinsic size
+            drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+        }
+    }
+
+    override fun onRemembered() {
+        drawable.callback = callback
+        drawable.setVisible(true, true)
+        if (drawable is Animatable) drawable.start()
+    }
+
+    override fun onAbandoned(): Unit = onForgotten()
+
+    override fun onForgotten() {
+        if (drawable is Animatable) drawable.stop()
+        drawable.setVisible(false, false)
+        drawable.callback = null
+    }
+
+    override fun applyAlpha(alpha: Float): Boolean {
+        drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+        return true
+    }
+
+    override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+        drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+        return true
+    }
+
+    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+        if (Build.VERSION.SDK_INT >= 23) {
+            return drawable.setLayoutDirection(
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+                    LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+                }
+            )
+        }
+        return false
+    }
+
+    override val intrinsicSize: Size
+        get() = drawableIntrinsicSize
+
+    override fun DrawScope.onDraw() {
+        drawIntoCanvas { canvas ->
+            // Reading this ensures that we invalidate when invalidateDrawable() is called
+            drawInvalidateTick
+
+            // Update the Drawable's bounds
+            drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+            canvas.withSave { drawable.draw(canvas.nativeCanvas) }
+        }
+    }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable
+ * contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from within
+ * Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+public fun rememberDrawablePainter(drawable: Drawable?): Painter =
+    remember(drawable) {
+        when (drawable) {
+            null -> EmptyPainter
+            is ColorDrawable -> ColorPainter(Color(drawable.color))
+            // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+            // will receive the necessary events
+            else -> DrawablePainter(drawable.mutate())
+        }
+    }
+
+private val Drawable.intrinsicSize: Size
+    get() =
+        when {
+            // Only return a finite size if the drawable has an intrinsic size
+            intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+                Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+            }
+            else -> Size.Unspecified
+        }
+
+internal object EmptyPainter : Painter() {
+    override val intrinsicSize: Size
+        get() = Size.Unspecified
+    override fun DrawScope.onDraw() {}
+}
diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
index 8e9c586..6e7a142 100644
--- a/packages/SystemUI/compose/core/tests/Android.bp
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 3c32594..9a34d6f 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.compose
 
-import android.app.Dialog
 import android.content.Context
 import android.view.View
 import android.view.WindowInsets
@@ -28,12 +27,13 @@
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.StateFlow
@@ -90,10 +90,10 @@
         throwComposeUnavailableError()
     }
 
-    override fun createStickyKeysDialog(
-        dialogFactory: SystemUIDialogFactory,
+    override fun createStickyKeysIndicatorContent(
+        context: Context,
         viewModel: StickyKeysIndicatorViewModel
-    ): Dialog {
+    ): View {
         throwComposeUnavailableError()
     }
 
@@ -114,6 +114,12 @@
         dialogFactory: BouncerDialogFactory,
     ): View = throwComposeUnavailableError()
 
+    override fun createLockscreen(
+        context: Context,
+        viewModel: LockscreenContentViewModel,
+        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+    ): View = throwComposeUnavailableError()
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 725aef2..fc3912e 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -16,6 +16,16 @@
 
 package com.android.systemui.scene
 
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 import dagger.Module
+import dagger.Provides
 
-@Module interface LockscreenSceneModule
+@Module
+interface LockscreenSceneModule {
+    companion object {
+        @Provides
+        fun providesLockscreenBlueprints(): Set<LockscreenSceneBlueprint> {
+            return emptySet()
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index afb860e..51d2a03 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -16,31 +16,37 @@
 
 package com.android.systemui.compose
 
-import android.app.Dialog
 import android.content.Context
 import android.graphics.Point
 import android.view.View
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
 import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.composable.BouncerContent
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.keyboard.stickykeys.ui.view.StickyKeysIndicator
+import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -50,8 +56,6 @@
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.scene.ui.composable.SceneContainer
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
-import com.android.systemui.statusbar.phone.create
 import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import kotlinx.coroutines.CoroutineScope
@@ -126,8 +130,9 @@
         return ComposeView(context).apply {
             setContent {
                 PlatformTheme {
-                    DisplayCutoutProvider(
-                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
+                    ScreenDecorProvider(
+                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
+                        screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
                     ) {
                         SceneContainer(
                             viewModel = viewModel,
@@ -140,11 +145,11 @@
         }
     }
 
-    override fun createStickyKeysDialog(
-        dialogFactory: SystemUIDialogFactory,
+    override fun createStickyKeysIndicatorContent(
+        context: Context,
         viewModel: StickyKeysIndicatorViewModel
-    ): Dialog {
-        return dialogFactory.create { StickyKeysIndicator(viewModel) }
+    ): View {
+        return createStickyKeyIndicatorView(context, viewModel)
     }
 
     override fun createCommunalView(
@@ -214,4 +219,19 @@
             setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
         }
     }
+
+    override fun createLockscreen(
+        context: Context,
+        viewModel: LockscreenContentViewModel,
+        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+    ): View {
+        val sceneBlueprints =
+            blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
+        return ComposeView(context).apply {
+            setContent {
+                LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
+                    .Content(modifier = Modifier.fillMaxSize())
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index cbf2496..f5dc154 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -20,8 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewConfigurator
 import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.composable.LockscreenScene
 import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.scene.shared.model.Scene
 import dagger.Binds
 import dagger.Module
@@ -51,5 +53,12 @@
         ): () -> View {
             return { configurator.get().getKeyguardRootView() }
         }
+
+        @Provides
+        fun providesLockscreenBlueprints(
+            blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>
+        ): Set<LockscreenSceneBlueprint> {
+            return blueprints
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 5ab2235..c12084d 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
similarity index 70%
rename from packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index ed393c0..76bd4ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -21,17 +21,28 @@
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.flow.StateFlow
 
 /** The bounds and [CutoutLocation] of the current display. */
 val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
 
+/** The corner radius in px of the current display. */
+val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
+
 @Composable
-fun DisplayCutoutProvider(
+fun ScreenDecorProvider(
     displayCutout: StateFlow<DisplayCutout>,
+    screenCornerRadius: Float,
     content: @Composable () -> Unit,
 ) {
     val cutout by displayCutout.collectAsState()
-
-    CompositionLocalProvider(LocalDisplayCutout provides cutout) { content() }
+    val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
+    CompositionLocalProvider(
+        LocalScreenCornerRadius provides screenCornerRadiusDp,
+        LocalDisplayCutout provides cutout
+    ) {
+        content()
+    }
 }
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 576596f..4e72dfe 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
@@ -160,14 +160,23 @@
                         gridCoordinates
                     ) {
                         detectLongPressGesture { offset ->
-                            isButtonToEditWidgetsShowing = true
-
                             // Deduct both grid offset relative to its container and content offset.
                             val adjustedOffset =
                                 gridCoordinates?.let {
                                     offset - it.positionInWindow() - contentOffset
                                 }
                             val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+                            // Display the button only when the gesture initiates from widgets,
+                            // the CTA tile, or an empty area on the screen. UMO/smartspace have
+                            // their own long-press handlers. To prevent user confusion, we should
+                            // not display this button.
+                            if (
+                                index == null ||
+                                    communalContent[index].isWidget() ||
+                                    communalContent[index] is CommunalContentModel.CtaTileInViewMode
+                            ) {
+                                isButtonToEditWidgetsShowing = true
+                            }
                             val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
                             viewModel.setSelectedKey(key)
                         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 68e57b5..dd86646 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -16,23 +16,42 @@
 
 package com.android.systemui.keyboard.stickykeys.ui.view
 
+import android.content.Context
+import android.view.View
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
+import com.android.compose.theme.PlatformTheme
 import com.android.systemui.keyboard.stickykeys.shared.model.Locked
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 
+fun createStickyKeyIndicatorView(context: Context, viewModel: StickyKeysIndicatorViewModel): View {
+    return ComposeView(context).apply {
+        setContent {
+            PlatformTheme {
+                val defaultContentColor = MaterialTheme.colorScheme.onSurfaceVariant
+                CompositionLocalProvider(LocalContentColor provides defaultContentColor) {
+                    StickyKeysIndicator(viewModel)
+                }
+            }
+        }
+    }
+}
+
 @Composable
 fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) {
     val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap())
@@ -53,7 +72,7 @@
             stickyKeys.forEach { (key, isLocked) ->
                 key(key) {
                     Text(
-                        text = key.text,
+                        text = key.displayedText,
                         fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
                     )
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 2cb0034..b5499b7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -25,7 +25,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.transitions
-import com.android.systemui.keyguard.ui.composable.blueprint.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import javax.inject.Inject
 
@@ -39,10 +39,10 @@
 @Inject
 constructor(
     private val viewModel: LockscreenContentViewModel,
-    private val blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+    private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
 ) {
 
-    private val sceneKeyByBlueprint: Map<LockscreenSceneBlueprint, SceneKey> by lazy {
+    private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
         blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
     }
     private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 86124c6..6b210af 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -36,7 +36,7 @@
 @Inject
 constructor(
     private val viewModel: LockscreenContentViewModel,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "communal"
 
@@ -59,5 +59,5 @@
 
 @Module
 interface CommunalBlueprintModule {
-    @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): LockscreenSceneBlueprint
+    @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): ComposableLockscreenSceneBlueprint
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
similarity index 87%
rename from packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
index 6d9cba4..cb73983 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
@@ -19,13 +19,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 
 /** Defines interface for classes that can render the content for a specific blueprint/layout. */
-interface LockscreenSceneBlueprint {
-
-    /** The ID that uniquely identifies this blueprint across all other blueprints. */
-    val id: String
-
+interface ComposableLockscreenSceneBlueprint : LockscreenSceneBlueprint {
     /** Renders the content of this blueprint. */
     @Composable fun SceneScope.Content(modifier: Modifier)
 }
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 bf02d8a..a07ab4a 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
@@ -20,10 +20,12 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
@@ -38,6 +40,7 @@
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -61,7 +64,7 @@
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val clockInteractor: KeyguardClockInteractor,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "default"
 
@@ -84,6 +87,7 @@
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                         with(clockSection) {
                             SmallClock(
+                                burnInParams = burnIn.parameters,
                                 onTopChanged = burnIn.onSmallClockTopChanged,
                                 modifier = Modifier.fillMaxWidth(),
                             )
@@ -95,7 +99,13 @@
                                 modifier =
                                     Modifier.fillMaxWidth()
                                         .padding(
-                                            top = { viewModel.getSmartSpacePaddingTop(resources) }
+                                            top = { viewModel.getSmartSpacePaddingTop(resources) },
+                                        )
+                                        .padding(
+                                            bottom =
+                                                dimensionResource(
+                                                    R.dimen.keyguard_status_view_bottom_margin
+                                                ),
                                         ),
                             )
                         }
@@ -214,5 +224,5 @@
 
 @Module
 interface DefaultBlueprintModule {
-    @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): LockscreenSceneBlueprint
+    @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index d0aa444..b035e42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -20,10 +20,12 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
@@ -38,6 +40,7 @@
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -61,7 +64,7 @@
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val clockInteractor: KeyguardClockInteractor,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
 
@@ -86,6 +89,7 @@
                             SmallClock(
                                 onTopChanged = burnIn.onSmallClockTopChanged,
                                 modifier = Modifier.fillMaxWidth(),
+                                burnInParams = burnIn.parameters,
                             )
                         }
                         with(smartSpaceSection) {
@@ -96,6 +100,12 @@
                                     Modifier.fillMaxWidth()
                                         .padding(
                                             top = { viewModel.getSmartSpacePaddingTop(resources) }
+                                        )
+                                        .padding(
+                                            bottom =
+                                                dimensionResource(
+                                                    R.dimen.keyguard_status_view_bottom_margin
+                                                )
                                         ),
                             )
                         }
@@ -222,5 +232,5 @@
 interface ShortcutsBesideUdfpsBlueprintModule {
     @Binds
     @IntoSet
-    fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): LockscreenSceneBlueprint
+    fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): ComposableLockscreenSceneBlueprint
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 616a7b4..660fc5a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -72,7 +72,7 @@
     private val settingsMenuSection: SettingsMenuSection,
     private val clockInteractor: KeyguardClockInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
@@ -109,7 +109,14 @@
                                                 .padding(
                                                     top = {
                                                         viewModel.getSmartSpacePaddingTop(resources)
-                                                    }
+                                                    },
+                                                )
+                                                .padding(
+                                                    bottom =
+                                                        dimensionResource(
+                                                            R.dimen
+                                                                .keyguard_status_view_bottom_margin
+                                                        )
                                                 ),
                                     )
                                 }
@@ -237,5 +244,7 @@
 
 @Module
 interface SplitShadeBlueprintModule {
-    @Binds @IntoSet fun blueprint(blueprint: SplitShadeBlueprint): LockscreenSceneBlueprint
+    @Binds
+    @IntoSet
+    fun blueprint(blueprint: SplitShadeBlueprint): ComposableLockscreenSceneBlueprint
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index 8f21879..fa07baf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -33,7 +33,10 @@
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.customization.R as customizationR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import javax.inject.Inject
 
@@ -42,10 +45,12 @@
 constructor(
     private val viewModel: KeyguardClockViewModel,
     private val clockInteractor: KeyguardClockInteractor,
+    private val aodBurnInViewModel: AodBurnInViewModel,
 ) {
 
     @Composable
     fun SceneScope.SmallClock(
+        burnInParams: BurnInParameters,
         onTopChanged: (top: Float?) -> Unit,
         modifier: Modifier = Modifier,
     ) {
@@ -89,7 +94,11 @@
                                     dimensionResource(customizationR.dimen.clock_padding_start)
                             )
                             .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
-                            .onTopPlacementChanged(onTopChanged),
+                            .onTopPlacementChanged(onTopChanged)
+                            .burnInAware(
+                                viewModel = aodBurnInViewModel,
+                                params = burnInParams,
+                            ),
                     update = {
                         val newClockView = checkNotNull(currentClock).smallClock.view
                         it.removeAllViews()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 5e27d82..f387021 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -45,7 +45,7 @@
 class NotificationSection
 @Inject
 constructor(
-    @Application context: Context,
+    @Application private val context: Context,
     private val viewModel: NotificationsPlaceholderViewModel,
     controller: NotificationStackScrollLayoutController,
     sceneContainerFlags: SceneContainerFlags,
@@ -55,17 +55,22 @@
     notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
     ambientState: AmbientState,
     notificationStackSizeCalculator: NotificationStackSizeCalculator,
-    @Main mainDispatcher: CoroutineDispatcher,
+    @Main private val mainDispatcher: CoroutineDispatcher,
 ) {
+
     init {
         if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
-            // This scene container section moves the NSSL to the SharedNotificationContainer. This
-            //  also requires that SharedNotificationContainer gets moved to the SceneWindowRootView
-            //  by the SceneWindowRootViewBinder.
-            // Prior to Scene Container, but when the KeyguardShadeMigrationNssl flag is enabled,
-            //  NSSL is moved into this container by the NotificationStackScrollLayoutSection.
-            (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
-            sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+            // This scene container section moves the NSSL to the SharedNotificationContainer.
+            // This also requires that SharedNotificationContainer gets moved to the
+            // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
+            // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
+            // container by the NotificationStackScrollLayoutSection.
+            // Ensure stackScrollLayout is a child of sharedNotificationContainer.
+
+            if (stackScrollLayout.parent != sharedNotificationContainer) {
+                (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
+                sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+            }
 
             SharedNotificationContainerBinder.bind(
                 sharedNotificationContainer,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0e08a19..d70f82f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -51,22 +51,29 @@
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.height
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
 import com.android.systemui.scene.ui.composable.Gone
 import com.android.systemui.scene.ui.composable.Shade
 import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import kotlin.math.roundToInt
 
@@ -77,6 +84,13 @@
         val ShelfSpace = ElementKey("ShelfSpace")
     }
 
+    // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
+    // at its maximum, given they are at their minimum value at expansion = 0f.
+    object TransitionThresholds {
+        const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
+        const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
+    }
+
     enum class Form {
         HunFromTop,
         Stack,
@@ -125,19 +139,19 @@
     modifier: Modifier = Modifier,
 ) {
     val density = LocalDensity.current
-    val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+    val screenCornerRadius = LocalScreenCornerRadius.current
     val expansionFraction by viewModel.expandFraction.collectAsState(0f)
 
     val navBarHeight =
         with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
-    val statusBarHeight =
-        with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
-    val displayCutoutHeight =
-        with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+    val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+    val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
     val screenHeight =
-        with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
-            navBarHeight +
-            maxOf(statusBarHeight, displayCutoutHeight)
+        with(density) {
+            (LocalConfiguration.current.screenHeightDp.dp +
+                    maxOf(statusBarHeight, displayCutoutHeight))
+                .toPx()
+        } + navBarHeight
 
     val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
 
@@ -171,26 +185,53 @@
             .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
     }
 
-    Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+    Box(
+        modifier =
+            modifier
+                .element(Notifications.Elements.NotificationScrim)
+                .offset {
+                    // if scrim is expanded while transitioning to Gone scene, increase the offset
+                    // in step with the transition so that it is 0 when it completes.
+                    if (
+                        scrimOffset.value < 0 &&
+                            layoutState.isTransitioning(from = Shade, to = Gone)
+                    ) {
+                        IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+                    } else {
+                        IntOffset(x = 0, y = scrimOffset.value.roundToInt())
+                    }
+                }
+                .graphicsLayer {
+                    shape =
+                        calculateCornerRadius(
+                                screenCornerRadius,
+                                { expansionFraction },
+                                layoutState.isTransitioningBetween(Gone, Shade)
+                            )
+                            .let {
+                                RoundedCornerShape(
+                                    topStart = it,
+                                    topEnd = it,
+                                )
+                            }
+                    clip = true
+                }
+    ) {
+        // Creates a cutout in the background scrim in the shape of the notifications scrim.
+        // Only visible when notif scrim alpha < 1, during shade expansion.
         Spacer(
             modifier =
-                Modifier.fillMaxSize()
-                    .graphicsLayer {
-                        shape = RoundedCornerShape(cornerRadius.dp)
-                        clip = true
-                    }
-                    .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
+                Modifier.fillMaxSize().drawBehind {
+                    drawRect(Color.Black, blendMode = BlendMode.DstOut)
+                }
         )
         Box(
             modifier =
                 Modifier.fillMaxSize()
-                    .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
                     .graphicsLayer {
-                        shape = RoundedCornerShape(cornerRadius.dp)
-                        clip = true
                         alpha =
                             if (layoutState.isTransitioningBetween(Gone, Shade)) {
-                                (expansionFraction / 0.3f).coerceAtMost(1f)
+                                (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
                             } else 1f
                     }
                     .background(MaterialTheme.colorScheme.surface)
@@ -278,10 +319,10 @@
                 .onSizeChanged { size: IntSize ->
                     debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
                 }
-                .onPlaced { coordinates: LayoutCoordinates ->
+                .onGloballyPositioned { coordinates: LayoutCoordinates ->
                     viewModel.onContentTopChanged(coordinates.positionInWindow().y)
                     debugLog(viewModel) {
-                        "STACK onPlaced:" +
+                        "STACK onGloballyPositioned:" +
                             " size=${coordinates.size}" +
                             " position=${coordinates.positionInWindow()}" +
                             " bounds=${coordinates.boundsInWindow()}"
@@ -310,6 +351,23 @@
     }
 }
 
+private fun calculateCornerRadius(
+    screenCornerRadius: Dp,
+    expansionFraction: () -> Float,
+    transitioning: Boolean,
+): Dp {
+    return if (transitioning) {
+        lerp(
+                start = screenCornerRadius.value,
+                stop = SCRIM_CORNER_RADIUS,
+                fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceAtMost(1f),
+            )
+            .dp
+    } else {
+        SCRIM_CORNER_RADIUS.dp
+    }
+}
+
 private inline fun debugLog(
     viewModel: NotificationsPlaceholderViewModel,
     msg: () -> Any,
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 5531f9c..969dec3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,7 +36,6 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -47,11 +46,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.positionInWindow
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
@@ -62,7 +56,6 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.SceneKey
@@ -122,8 +115,6 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
-
     // TODO(b/280887232): implement the real UI.
     Box(modifier = modifier.fillMaxSize()) {
         val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -155,9 +146,9 @@
         // a background that extends to the edges.
         Spacer(
             modifier =
-                Modifier.element(Shade.Elements.ScrimBackground)
+                Modifier.element(Shade.Elements.BackgroundScrim)
                     .fillMaxSize()
-                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+                    .background(MaterialTheme.colorScheme.scrim)
         )
         Column(
             horizontalAlignment = Alignment.CenterHorizontally,
@@ -242,32 +233,5 @@
                 }
             }
         }
-        // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
-        // transition from Shade scene.
-        Box(
-            modifier =
-                Modifier.element(Notifications.Elements.NotificationScrim)
-                    .fillMaxWidth()
-                    .height(0.dp)
-                    .graphicsLayer {
-                        shape = RoundedCornerShape(cornerRadius.dp)
-                        clip = true
-                        alpha = 1f
-                    }
-                    .background(MaterialTheme.colorScheme.surface)
-                    .align(Alignment.BottomCenter)
-                    .onPlaced { coordinates: LayoutCoordinates ->
-                        viewModel.notifications.onContentTopChanged(
-                            coordinates.positionInWindow().y
-                        )
-                        val boundsInWindow = coordinates.boundsInWindow()
-                        viewModel.notifications.onBoundsChanged(
-                            left = boundsInWindow.left,
-                            top = boundsInWindow.top,
-                            right = boundsInWindow.right,
-                            bottom = boundsInWindow.bottom,
-                        )
-                    }
-        )
     }
 }
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 770d654..736ee1f 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
@@ -16,18 +16,18 @@
 
 package com.android.systemui.scene.ui.composable
 
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.shade.ui.composable.Shade
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -63,6 +63,6 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
+        Spacer(modifier.fillMaxSize())
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 0c2c519..1223ace 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,6 +3,7 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.shade.ui.composable.ShadeHeader
 
@@ -10,5 +11,6 @@
     spec = tween(durationMillis = 500)
 
     fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
-    translate(QuickSettings.Elements.Content, Edge.Top, true)
+    translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+    translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index ebc343d..2d5cf5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -10,9 +10,8 @@
 fun TransitionBuilder.lockscreenToShadeTransition() {
     spec = tween(durationMillis = 500)
 
-    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
     fractionRange(end = 0.5f) {
-        fade(Shade.Elements.ScrimBackground)
+        fade(Shade.Elements.BackgroundScrim)
         translate(
             QuickSettings.Elements.CollapsedGrid,
             Edge.Top,
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 497fe87..677df7e 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
@@ -38,6 +38,7 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
@@ -74,8 +75,8 @@
     object Elements {
         val QuickSettings = ElementKey("ShadeQuickSettings")
         val MediaCarousel = ElementKey("ShadeMediaCarousel")
-        val Scrim = ElementKey("ShadeScrim")
-        val ScrimBackground = ElementKey("ShadeScrimBackground")
+        val BackgroundScrim =
+            ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
     }
 
     object Dimensions {
@@ -162,7 +163,9 @@
 
     Box(
         modifier =
-            modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+            modifier
+                .element(Shade.Elements.BackgroundScrim)
+                .background(MaterialTheme.colorScheme.scrim),
     )
     Box {
         Layout(
@@ -236,17 +239,7 @@
             check(measurables[1].size == 1)
 
             val quickSettingsPlaceable = measurables[0][0].measure(constraints)
-
-            val notificationsMeasurable = measurables[1][0]
-            val notificationsScrimMaxHeight =
-                constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
-            val notificationsPlaceable =
-                notificationsMeasurable.measure(
-                    constraints.copy(
-                        minHeight = notificationsScrimMaxHeight,
-                        maxHeight = notificationsScrimMaxHeight
-                    )
-                )
+            val notificationsPlaceable = measurables[1][0].measure(constraints)
 
             maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index dcd22fe..a7ec93f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Slider
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
@@ -33,6 +34,7 @@
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(20.dp),
     ) {
+        Slider(0.5f, {})
         for (component in components) {
             AnimatedVisibility(component.isVisible) {
                 with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp
index c85cd7b..69b18c4 100644
--- a/packages/SystemUI/compose/features/tests/Android.bp
+++ b/packages/SystemUI/compose/features/tests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index b26194f..37d763b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -30,7 +30,8 @@
 import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.lerp
-import com.android.compose.ui.util.lerp
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
 
 /**
  * A [State] whose [value] is animated.
@@ -282,7 +283,7 @@
                 } else {
                     val progress =
                         if (canOverflow) transition.progress
-                        else transition.progress.coerceIn(0f, 1f)
+                        else transition.progress.fastCoerceIn(0f, 1f)
                     lerp(fromValue, toValue, progress)
                 }
             } else fromValue ?: toValue
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 a910bca..828e34d 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
@@ -26,6 +26,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isUnspecified
 import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.graphics.CompositingStrategy
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.layout.IntermediateMeasureScope
@@ -38,6 +39,8 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.ui.util.lerp
@@ -361,7 +364,7 @@
             isSpecified = { true },
             ::lerp,
         )
-        .coerceIn(0f, 1f)
+        .fastCoerceIn(0f, 1f)
 }
 
 @OptIn(ExperimentalComposeUiApi::class)
@@ -473,7 +476,8 @@
             placeable.place(offset)
         } else {
             placeable.placeWithLayer(offset) {
-                this.alpha = elementAlpha(layoutImpl, element, scene)
+                alpha = elementAlpha(layoutImpl, element, scene)
+                compositingStrategy = CompositingStrategy.ModulateAlpha
             }
         }
     }
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 529fc03..3ff869b 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
@@ -283,21 +283,28 @@
             }
 
             onDragStart(drag.position, overSlop, pressed.size)
-            onDrag(drag, overSlop)
 
-            val successful =
-                when (orientation) {
-                    Orientation.Horizontal ->
-                        horizontalDrag(drag.id) {
-                            onDrag(it, it.positionChange().x)
-                            it.consume()
-                        }
-                    Orientation.Vertical ->
-                        verticalDrag(drag.id) {
-                            onDrag(it, it.positionChange().y)
-                            it.consume()
-                        }
-                }
+            val successful: Boolean
+            try {
+                onDrag(drag, overSlop)
+
+                successful =
+                    when (orientation) {
+                        Orientation.Horizontal ->
+                            horizontalDrag(drag.id) {
+                                onDrag(it, it.positionChange().x)
+                                it.consume()
+                            }
+                        Orientation.Vertical ->
+                            verticalDrag(drag.id) {
+                                onDrag(it, it.positionChange().y)
+                                it.consume()
+                            }
+                    }
+            } catch (t: Throwable) {
+                onDragCancel()
+                throw t
+            }
 
             if (successful) {
                 onDragEnd()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 23af5ac..b3d2bc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -701,27 +701,19 @@
                         gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
-                val swipeTransition = gestureHandler.swipeTransition
-                val progress = swipeTransition.progress
                 val threshold = layoutImpl.transitionInterceptionThreshold
-                fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
-                // The transition is always between 0 and 1. If it is close to either of these
-                // intervals, we want to go directly to the TransitionState.Idle.
-                // The progress value can go beyond this range in the case of overscroll.
-                val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
-                if (shouldSnapToIdle) {
-                    swipeTransition.cancelOffsetAnimation()
-                    layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
+                val hasSnappedToIdle = layoutState.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
                 }
 
-                // Start only if we cannot consume this event
-                val canStart = !shouldSnapToIdle
-                if (canStart) {
-                    isIntercepting = true
-                }
-
-                canStart
+                // 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
             },
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                 val behavior: NestedScrollBehavior =
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 5258078..d904c8b 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
@@ -504,6 +504,7 @@
 
         layoutImpl.density = density
         layoutImpl.swipeSourceDetector = swipeSourceDetector
+        layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
     }
 
     layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index aee6f9e..a8da551 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
@@ -24,6 +24,11 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastForEach
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
+import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 
@@ -100,8 +105,9 @@
 fun MutableSceneTransitionLayoutState(
     initialScene: SceneKey,
     transitions: SceneTransitions = SceneTransitions.Empty,
+    stateLinks: List<StateLink> = emptyList(),
 ): MutableSceneTransitionLayoutState {
-    return MutableSceneTransitionLayoutStateImpl(initialScene, transitions)
+    return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
 }
 
 /**
@@ -120,9 +126,12 @@
     currentScene: SceneKey,
     onChangeScene: (SceneKey) -> Unit,
     transitions: SceneTransitions = SceneTransitions.Empty,
+    stateLinks: List<StateLink> = emptyList(),
 ): SceneTransitionLayoutState {
-    return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) }
-        .apply { update(currentScene, onChangeScene, transitions) }
+    return remember {
+            HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+        }
+        .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
 }
 
 @Stable
@@ -183,8 +192,10 @@
     }
 }
 
-internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
-    SceneTransitionLayoutState {
+internal abstract class BaseSceneTransitionLayoutState(
+    initialScene: SceneKey,
+    protected var stateLinks: List<StateLink>,
+) : SceneTransitionLayoutState {
     override var transitionState: TransitionState by
         mutableStateOf(TransitionState.Idle(initialScene))
         protected set
@@ -195,6 +206,8 @@
      */
     internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
 
+    private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
     /**
      * Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
      *
@@ -223,19 +236,94 @@
             transitions
                 .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
                 .transformationSpec()
-
+        cancelActiveTransitionLinks()
+        setupTransitionLinks(transition)
         transitionState = transition
     }
 
+    private fun cancelActiveTransitionLinks() {
+        for ((link, linkedTransition) in activeTransitionLinks) {
+            link.target.finishTransition(linkedTransition, linkedTransition.currentScene)
+        }
+        activeTransitionLinks.clear()
+    }
+
+    private fun setupTransitionLinks(transitionState: TransitionState) {
+        if (transitionState !is TransitionState.Transition) return
+        stateLinks.fastForEach { stateLink ->
+            val matchingLinks =
+                stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) }
+            if (matchingLinks.isEmpty()) return@fastForEach
+            if (matchingLinks.size > 1) error("More than one link matched.")
+
+            val targetCurrentScene = stateLink.target.transitionState.currentScene
+            val matchingLink = matchingLinks[0]
+
+            if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+
+            val linkedTransition =
+                LinkedTransition(
+                    originalTransition = transitionState,
+                    fromScene = targetCurrentScene,
+                    toScene = matchingLink.targetTo,
+                )
+
+            stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey)
+            activeTransitionLinks[stateLink] = linkedTransition
+        }
+    }
+
     /**
      * Notify that [transition] was finished and that we should settle to [idleScene]. This will do
      * nothing if [transition] was interrupted since it was started.
      */
     internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+        resolveActiveTransitionLinks(idleScene)
         if (transitionState == transition) {
             transitionState = TransitionState.Idle(idleScene)
         }
     }
+
+    private fun resolveActiveTransitionLinks(idleScene: SceneKey) {
+        val previousTransition = this.transitionState as? TransitionState.Transition ?: return
+        for ((link, linkedTransition) in activeTransitionLinks) {
+            if (previousTransition.fromScene == idleScene) {
+                // The transition ended by arriving at the fromScene, move link to Idle(fromScene).
+                link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+            } else if (previousTransition.toScene == idleScene) {
+                // The transition ended by arriving at the toScene, move link to Idle(toScene).
+                link.target.finishTransition(linkedTransition, linkedTransition.toScene)
+            } else {
+                // The transition was interrupted by something else, we reset to initial state.
+                link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+            }
+        }
+        activeTransitionLinks.clear()
+    }
+
+    /**
+     * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
+     * to the closest scene.
+     *
+     * @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
+
+        return when {
+            isProgressCloseTo(0f) -> {
+                finishTransition(transition, transition.fromScene)
+                true
+            }
+            isProgressCloseTo(1f) -> {
+                finishTransition(transition, transition.toScene)
+                true
+            }
+            else -> false
+        }
+    }
 }
 
 /**
@@ -246,7 +334,8 @@
     initialScene: SceneKey,
     override var transitions: SceneTransitions,
     private var changeScene: (SceneKey) -> Unit,
-) : BaseSceneTransitionLayoutState(initialScene) {
+    stateLinks: List<StateLink> = emptyList(),
+) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
     private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
 
     override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
@@ -256,10 +345,12 @@
         currentScene: SceneKey,
         onChangeScene: (SceneKey) -> Unit,
         transitions: SceneTransitions,
+        stateLinks: List<StateLink>,
     ) {
         SideEffect {
             this.changeScene = onChangeScene
             this.transitions = transitions
+            this.stateLinks = stateLinks
 
             targetSceneChannel.trySend(currentScene)
         }
@@ -283,7 +374,8 @@
 internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
     override var transitions: SceneTransitions,
-) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
+    stateLinks: List<StateLink> = emptyList(),
+) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
     override fun setTargetScene(
         targetScene: SceneKey,
         coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 04254fb..603f7ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -16,6 +16,9 @@
 
 package com.android.compose.animation.scene.transformation
 
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import androidx.compose.ui.util.fastCoerceIn
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
@@ -106,10 +109,10 @@
     fun progress(transitionProgress: Float): Float {
         return when {
             start.isSpecified() && end.isSpecified() ->
-                ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+                ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
             !start.isSpecified() && !end.isSpecified() -> transitionProgress
-            end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
-            else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+            end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
+            else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
         }
     }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
new file mode 100644
index 0000000..33b57b2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A linked transition which is driven by a [originalTransition]. */
+internal class LinkedTransition(
+    private val originalTransition: TransitionState.Transition,
+    fromScene: SceneKey,
+    toScene: SceneKey,
+) : TransitionState.Transition(fromScene, toScene) {
+
+    override val currentScene: SceneKey
+        get() {
+            return when (originalTransition.currentScene) {
+                originalTransition.fromScene -> fromScene
+                originalTransition.toScene -> toScene
+                else -> error("Original currentScene is neither FromScene nor ToScene")
+            }
+        }
+
+    override val isInitiatedByUserInput: Boolean
+        get() = originalTransition.isInitiatedByUserInput
+
+    override val isUserInputOngoing: Boolean
+        get() = originalTransition.isUserInputOngoing
+
+    override val progress: Float
+        get() = originalTransition.progress
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
new file mode 100644
index 0000000..6c29946
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
+class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
+
+    internal val target = target as BaseSceneTransitionLayoutState
+
+    /**
+     * Links two transitions (source and target) together.
+     *
+     * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
+     * `null`, `SceneA` means that any transition at the source will trigger a transition in the
+     * target to `SceneA` from any current scene.
+     */
+    class TransitionLink(
+        val sourceFrom: SceneKey?,
+        val sourceTo: SceneKey?,
+        val targetFrom: SceneKey?,
+        val targetTo: SceneKey,
+        val targetTransitionKey: TransitionKey? = null,
+    ) {
+        init {
+            if (
+                (sourceFrom != null && sourceFrom == sourceTo) ||
+                    (targetFrom != null && targetFrom == targetTo)
+            )
+                error("From and To can't be the same")
+        }
+
+        internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
+            return (sourceFrom == null || sourceFrom == transition.fromScene) &&
+                (sourceTo == null || sourceTo == transition.toScene)
+        }
+
+        internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
+            return (targetFrom == null || targetFrom == targetCurrentScene) &&
+                targetTo != targetCurrentScene
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index 13747b7..e78ab29 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
@@ -20,24 +20,8 @@
 import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.Scale
-import kotlin.math.roundToInt
-import kotlin.math.roundToLong
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Float, stop: Float, fraction: Float): Float {
-    return (1 - fraction) * start + fraction * stop
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Int, stop: Int, fraction: Float): Int {
-    return start + ((stop - start) * fraction.toDouble()).roundToInt()
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Long, stop: Long, fraction: Float): Long {
-    return start + ((stop - start) * fraction.toDouble()).roundToLong()
-}
 
 /** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
 fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index a116501..e8854cf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -30,8 +30,8 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.ui.util.lerp
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
 import org.junit.Rule
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
new file mode 100644
index 0000000..cd99d05
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MultiPointerDraggableTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun cancellingPointerCallsOnDragStopped() {
+        val size = 200f
+        val middle = Offset(size / 2f, size / 2f)
+
+        var enabled by mutableStateOf(false)
+        var started = false
+        var dragged = false
+        var stopped = false
+
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { enabled },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ -> started = true },
+                        onDragDelta = { _ -> dragged = true },
+                        onDragStopped = { stopped = true },
+                    )
+            )
+        }
+
+        fun startDraggingDown() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(0f, touchSlop))
+            }
+        }
+
+        fun releaseFinger() {
+            rule.onRoot().performTouchInput { up() }
+        }
+
+        // Swiping down does nothing because enabled is false.
+        startDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+        releaseFinger()
+
+        // Enable the draggable and swipe down. This should both call onDragStarted() and
+        // onDragDelta().
+        enabled = true
+        rule.waitForIdle()
+        startDraggingDown()
+        assertThat(started).isTrue()
+        assertThat(dragged).isTrue()
+        assertThat(stopped).isFalse()
+
+        // Disable the pointer input. This should call onDragStopped() even if didn't release the
+        // finger yet.
+        enabled = false
+        rule.waitForIdle()
+        assertThat(started).isTrue()
+        assertThat(dragged).isTrue()
+        assertThat(stopped).isTrue()
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0ec..2dc94a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -47,7 +49,7 @@
 @RunWith(AndroidJUnit4::class)
 class SceneGestureHandlerTest {
     private class TestGestureScope(
-        val coroutineScope: MonotonicClockTestScope,
+        private val testScope: MonotonicClockTestScope,
     ) {
         private val layoutState =
             MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@
                     swipeSourceDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
                     builder = scenesBuilder,
-                    coroutineScope = coroutineScope,
+                    coroutineScope = testScope,
                 )
                 .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
 
         val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
         val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
-        val draggable = sceneGestureHandler.draggable
 
         fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
             SceneNestedScrollHandler(
-                    layoutImpl,
+                    layoutImpl = layoutImpl,
                     orientation = sceneGestureHandler.orientation,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@
         fun up(fractionOfScreen: Float) =
             if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
 
-        // Offset y: 10% of the screen
-        val offsetY10 = Offset(x = 0f, y = down(0.1f))
+        fun downOffset(fractionOfScreen: Float) =
+            if (fractionOfScreen < 0f) {
+                error("upOffset() is required, not implemented yet")
+            } else {
+                Offset(x = 0f, y = down(fractionOfScreen))
+            }
 
         val transitionState: TransitionState
             get() = layoutState.transitionState
@@ -127,15 +132,15 @@
             get() = (transitionState as Transition).progress
 
         fun advanceUntilIdle() {
-            coroutineScope.testScheduler.advanceUntilIdle()
+            testScope.testScheduler.advanceUntilIdle()
         }
 
         fun runCurrent() {
-            coroutineScope.testScheduler.runCurrent()
+            testScope.testScheduler.runCurrent()
         }
 
         fun assertIdle(currentScene: SceneKey) {
-            assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+            assertThat(transitionState).isInstanceOf(Idle::class.java)
             assertWithMessage("currentScene does not match")
                 .that(transitionState.currentScene)
                 .isEqualTo(currentScene)
@@ -148,11 +153,8 @@
             progress: Float? = null,
             isUserInputOngoing: Boolean? = null
         ) {
-            val transition = transitionState
-            assertWithMessage("transitionState must be Transition")
-                .that(transition is Transition)
-                .isTrue()
-            transition as Transition
+            assertThat(transitionState).isInstanceOf(Transition::class.java)
+            val transition = transitionState as Transition
 
             if (currentScene != null)
                 assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@
                     .that(transition.isUserInputOngoing)
                     .isEqualTo(isUserInputOngoing)
         }
+
+        fun onDragStarted(
+            startedPosition: Offset = Offset.Zero,
+            overSlop: Float,
+            pointersDown: Int = 1
+        ) {
+            // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+            if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+            onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+        }
+
+        fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+            onDragStarted(
+                sceneGestureHandler.draggable,
+                startedPosition,
+                overSlop = 0f,
+                pointersDown
+            )
+        }
+
+        fun onDragStarted(
+            draggableHandler: DraggableHandler,
+            startedPosition: Offset = Offset.Zero,
+            overSlop: Float = 0f,
+            pointersDown: Int = 1
+        ) {
+            draggableHandler.onDragStarted(
+                startedPosition = startedPosition,
+                overSlop = overSlop,
+                pointersDown = pointersDown,
+            )
+
+            // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+            onDelta(pixels = overSlop)
+        }
+
+        fun onDelta(pixels: Float) {
+            sceneGestureHandler.draggable.onDelta(pixels = pixels)
+        }
+
+        fun onDragStopped(velocity: Float) {
+            sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+            runCurrent()
+        }
+
+        fun NestedScrollConnection.scroll(
+            available: Offset,
+            consumedByScroll: Offset = Offset.Zero,
+        ) {
+            val consumedByPreScroll =
+                onPreScroll(
+                    available = available,
+                    source = NestedScrollSource.Drag,
+                )
+            val consumed = consumedByPreScroll + consumedByScroll
+
+            onPostScroll(
+                consumed = consumed,
+                available = available - consumed,
+                source = NestedScrollSource.Drag
+            )
+        }
+
+        fun NestedScrollConnection.preFling(
+            available: Velocity,
+            coroutineScope: CoroutineScope = testScope,
+        ) {
+            // onPreFling is a suspend function that returns the consumed velocity once it finishes
+            // consuming it. In the current scenario, it returns after completing the animation.
+            // To return immediately, we can initiate a job that allows us to check the status
+            // before the animation starts.
+            coroutineScope.launch { onPreFling(available = available) }
+            runCurrent()
+        }
     }
 
     private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
-        runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
-    }
+        runMonotonicClockTest {
+            val testGestureScope = TestGestureScope(testScope = this)
 
-    private fun DraggableHandler.onDragStarted(
-        overSlop: Float = 0f,
-        startedPosition: Offset = Offset.Zero,
-    ) {
-        onDragStarted(startedPosition, overSlop)
-        // MultiPointerDraggable will always call onDelta with the initial overSlop right after
-        onDelta(overSlop)
+            // run the test
+            testGestureScope.block()
+        }
     }
 
     @Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
 
     @Test
     fun onDragStarted_shouldStartATransition() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
     }
 
     @Test
     fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
 
-        draggable.onDelta(pixels = down(0.1f))
+        onDelta(pixels = down(fractionOfScreen = 0.1f))
         assertThat(progress).isEqualTo(0.2f)
     }
 
     @Test
     fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(
-            velocity = velocityThreshold - 0.01f,
-        )
+        onDragStopped(velocity = velocityThreshold - 0.01f)
         assertTransition(currentScene = SceneA)
 
         // wait for the stop animation
@@ -230,11 +300,10 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(velocity = velocityThreshold)
-
+        onDragStopped(velocity = velocityThreshold)
         assertTransition(currentScene = SceneC)
 
         // wait for the stop animation
@@ -244,10 +313,10 @@
 
     @Test
     fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(velocity = 0f)
+        onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneA)
     }
@@ -255,7 +324,7 @@
     @Test
     fun onDragReversedDirection_changeToScene() = runGestureTest {
         // Drag A -> B with progress 0.6
-        draggable.onDragStarted(overSlop = -60f)
+        onDragStarted(overSlop = -60f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -264,7 +333,7 @@
         )
 
         // Reverse direction such that A -> C now with 0.4
-        draggable.onDelta(100f)
+        onDelta(pixels = 100f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -273,7 +342,7 @@
         )
 
         // After the drag stopped scene C should be committed
-        draggable.onDragStopped(velocity = velocityThreshold)
+        onDragStopped(velocity = velocityThreshold)
         assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
 
         // wait for the stop animation
@@ -283,9 +352,12 @@
 
     @Test
     fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
-        horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+        val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+        onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
         assertIdle(currentScene = SceneA)
-        horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+        onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
         assertIdle(currentScene = SceneA)
     }
 
@@ -294,7 +366,7 @@
         navigateToSceneC()
 
         // We are on SceneC which has no action in Down direction
-        draggable.onDragStarted(10f)
+        onDragStarted(overSlop = 10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -303,7 +375,7 @@
         )
 
         // Reverse drag direction, it will consume the previous drag
-        draggable.onDelta(-10f)
+        onDelta(pixels = -10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -312,7 +384,7 @@
         )
 
         // Continue reverse drag direction, it should record progress to Scene B
-        draggable.onDelta(-10f)
+        onDelta(pixels = -10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -326,7 +398,10 @@
         navigateToSceneC()
 
         // Start dragging from the bottom
-        draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+        onDragStarted(
+            startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+            overSlop = up(fractionOfScreen = 0.1f)
+        )
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -337,14 +412,14 @@
 
     @Test
     fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
-        draggable.onDragStarted(down(0.3f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
             toScene = SceneC,
             progress = 0.3f
         )
-        draggable.onDelta(up(0.3f))
+        onDelta(pixels = up(fractionOfScreen = 0.3f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -355,8 +430,8 @@
 
     private fun TestGestureScope.navigateToSceneC() {
         assertIdle(currentScene = SceneA)
-        draggable.onDragStarted(down(1f))
-        draggable.onDragStopped(0f)
+        onDragStarted(overSlop = down(fractionOfScreen = 1f))
+        onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneC)
     }
@@ -364,7 +439,7 @@
     @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
-        draggable.onDragStarted(overSlop = up(0.2f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -373,13 +448,13 @@
         )
 
         // Start animation A -> B with progress 0.2 -> 1.0
-        draggable.onDragStopped(velocity = -velocityThreshold)
+        onDragStopped(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
-        draggable.onDragStarted()
-        draggable.onDelta(up(1f))
+        onDragStartedImmediately()
+        onDelta(pixels = up(fractionOfScreen = 1f))
         assertTransition(
             currentScene = SceneB,
             fromScene = SceneB,
@@ -388,7 +463,7 @@
         )
 
         // After the drag stopped scene C should be committed
-        draggable.onDragStopped(velocity = -velocityThreshold)
+        onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
 
         // wait for the stop animation
@@ -398,9 +473,9 @@
 
     @Test
     fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
-        draggable.onDragStarted(overSlop = up(0.2f))
-        draggable.onDelta(up(0.2f))
-        draggable.onDragStopped(velocity = -velocityThreshold)
+        onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+        onDelta(pixels = up(fractionOfScreen = 0.2f))
+        onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@
         mutableUserActionsB.remove(Swipe.Down)
 
         // start accelaratedScroll and scroll over to B -> null
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.5f))
-        draggable.onDelta(up(0.5f))
+        onDragStartedImmediately()
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
 
         // 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
-        draggable.onDelta(up(0.5f))
-        draggable.onDragStopped(0f)
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        onDragStopped(velocity = 0f)
 
         advanceUntilIdle()
         assertIdle(SceneB)
 
         // These events can still come in after the animation has settled
-        draggable.onDelta(up(0.5f))
-        draggable.onDragStopped(0f)
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        onDragStopped(velocity = 0f)
         assertIdle(SceneB)
     }
 
     @Test
     fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
-        draggable.onDragStarted(up(0.1f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
         mutableUserActionsA[Swipe.Up] = SceneC
-        draggable.onDelta(up(0.1f))
+        onDelta(pixels = up(fractionOfScreen = 0.1f))
         // target stays B even though UserActions changed
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
-        draggable.onDragStopped(down(0.1f))
+        onDragStopped(velocity = down(fractionOfScreen = 0.1f))
         advanceUntilIdle()
 
         // now target changed to C for new drag
-        draggable.onDragStarted(up(0.1f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
     }
 
     @Test
     fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
-        draggable.onDragStarted(up(0.1f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
         mutableUserActionsA[Swipe.Up] = SceneC
-        draggable.onDelta(up(0.1f))
-        draggable.onDragStopped(down(0.1f))
+        onDelta(pixels = up(fractionOfScreen = 0.1f))
+        onDragStopped(velocity = down(fractionOfScreen = 0.1f))
 
         // now target changed to C for new drag that started before previous drag settled to Idle
-        draggable.onDragStarted(overSlop = 0f)
-        draggable.onDelta(up(0.1f))
+        onDragStartedImmediately()
+        onDelta(pixels = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
     }
 
     @Test
     fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(
-            velocity = velocityThreshold,
-        )
+        onDragStopped(velocity = velocityThreshold)
 
-        // The stop animation is not started yet
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
-        runCurrent()
-
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
-        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
         assertTransition(currentScene = SceneC)
+        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
 
         // Start a new gesture while the offset is animating
-        draggable.onDragStarted()
+        onDragStartedImmediately()
         assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
     }
 
     @Test
     fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+        nestedScroll.onPreScroll(
+            available = downOffset(fractionOfScreen = 0.1f),
+            source = NestedScrollSource.Drag
+        )
         assertIdle(currentScene = SceneA)
     }
 
@@ -509,40 +580,29 @@
         val consumed =
             nestedScroll.onPostScroll(
                 consumed = Offset.Zero,
-                available = offsetY10,
+                available = downOffset(fractionOfScreen = 0.1f),
                 source = NestedScrollSource.Drag
             )
 
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
-        assertThat(consumed).isEqualTo(offsetY10)
-    }
-
-    private fun NestedScrollConnection.scroll(
-        available: Offset,
-        consumedByScroll: Offset = Offset.Zero,
-    ) {
-        val consumedByPreScroll =
-            onPreScroll(available = available, source = NestedScrollSource.Drag)
-        val consumed = consumedByPreScroll + consumedByScroll
-        onPostScroll(
-            consumed = consumed,
-            available = available - consumed,
-            source = NestedScrollSource.Drag
-        )
+        assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
     }
 
     @Test
     fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
         assertThat(progress).isEqualTo(0.1f)
 
         // start intercept preScroll
         val consumed =
-            nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+            nestedScroll.onPreScroll(
+                available = downOffset(fractionOfScreen = 0.1f),
+                source = NestedScrollSource.Drag
+            )
         assertThat(progress).isEqualTo(0.2f)
 
         // do nothing on postScroll
@@ -553,12 +613,12 @@
         )
         assertThat(progress).isEqualTo(0.2f)
 
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         assertThat(progress).isEqualTo(0.3f)
         assertTransition(currentScene = SceneA)
     }
 
-    private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+    private fun TestGestureScope.preScrollAfterSceneTransition(
         firstScroll: Float,
         secondScroll: Float
     ) {
@@ -567,10 +627,13 @@
         nestedScroll.scroll(available = Offset(0f, firstScroll))
 
         // stop scene transition (start the "stop animation")
-        nestedScroll.onPreFling(available = Velocity.Zero)
+        nestedScroll.preFling(available = Velocity.Zero)
 
         // a pre scroll event, that could be intercepted by SceneGestureHandler
-        nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+        nestedScroll.onPreScroll(
+            available = Offset(0f, secondScroll),
+            source = NestedScrollSource.Drag
+        )
     }
 
     @Test
@@ -610,16 +673,17 @@
 
         preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
 
+        advanceUntilIdle()
         assertIdle(SceneB)
     }
 
     @Test
     fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        nestedScroll.onPreFling(available = Velocity.Zero)
+        nestedScroll.preFling(available = Velocity.Zero)
         assertTransition(currentScene = SceneA)
 
         // wait for the stop animation
@@ -627,15 +691,15 @@
         assertIdle(currentScene = SceneA)
     }
 
-    private suspend fun TestGestureScope.flingAfterScroll(
+    private fun TestGestureScope.flingAfterScroll(
         use: NestedScrollBehavior,
         idleAfterScroll: Boolean,
     ) {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
 
-        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
     }
 
     @Test
@@ -679,19 +743,22 @@
     }
 
     /** we started the scroll in the scene, then fling with the velocityThreshold */
-    private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+    private fun TestGestureScope.flingAfterScrollStartedInScene(
         use: NestedScrollBehavior,
         idleAfterScroll: Boolean,
     ) {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
         // scroll consumed in child
-        nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+        nestedScroll.scroll(
+            available = downOffset(fractionOfScreen = 0.1f),
+            consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+        )
 
         // scroll offsetY10 is all available for parents
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
 
-        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
     }
 
     @Test
@@ -732,20 +799,20 @@
 
     @Test
     fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
-        draggable.onDelta(down(0.1f))
+        onDelta(pixels = down(fractionOfScreen = 0.1f))
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
-        draggable.onDragStopped(velocityThreshold)
+        onDragStopped(velocity = velocityThreshold)
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
         assertIdle(currentScene = SceneA)
     }
 
@@ -753,8 +820,10 @@
     fun startNestedScrollWhileDragging() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
 
+        val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
         // Start a drag and then stop it, given that
-        draggable.onDragStarted(overSlop = up(0.1f))
+        onDragStarted(overSlop = up(0.1f))
 
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@
         assertThat(progress).isEqualTo(0.2f)
 
         // this should be ignored, we are scrolling now!
-        draggable.onDragStopped(-velocityThreshold)
+        onDragStopped(-velocityThreshold)
         assertTransition(currentScene = SceneA)
 
         nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@
         nestedScroll.scroll(available = -offsetY10)
         assertThat(progress).isEqualTo(0.4f)
 
-        nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
         assertTransition(currentScene = SceneB)
 
         // wait for the stop animation
@@ -788,7 +857,7 @@
 
         // Swipe up from the middle to transition to scene B.
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -802,7 +871,7 @@
         // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
         // should be 0f.
         assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
-        draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+        onDragStartedImmediately(startedPosition = middle)
 
         // We should have intercepted the transition, so the transition should be the same object.
         assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@
         // instead animate from C to A.
         val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
         assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
-        draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+        onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
 
         assertTransition(
             currentScene = SceneC,
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 c61917d..f81a7f2 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,10 +18,14 @@
 
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.animation.scene.transition.link.StateLink
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
@@ -31,93 +35,241 @@
 class SceneTransitionLayoutStateTest {
     @get:Rule val rule = createComposeRule()
 
+    class TestableTransition(
+        fromScene: SceneKey,
+        toScene: SceneKey,
+    ) : TransitionState.Transition(fromScene, toScene) {
+        override var currentScene: SceneKey = fromScene
+        override var progress: Float = 0.0f
+        override var isInitiatedByUserInput: Boolean = false
+        override var isUserInputOngoing: Boolean = false
+    }
+
     @Test
     fun isTransitioningTo_idle() {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
 
         assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
-        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
-            .isFalse()
+        assertThat(state.isTransitioning(from = SceneA)).isFalse()
+        assertThat(state.isTransitioning(to = SceneB)).isFalse()
+        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isFalse()
     }
 
     @Test
     fun isTransitioningTo_transition() {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
-        state.startTransition(
-            transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
-            transitionKey = null
-        )
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null)
 
         assertThat(state.isTransitioning()).isTrue()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
-        assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
-        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
-        assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+        assertThat(state.isTransitioning(from = SceneA)).isTrue()
+        assertThat(state.isTransitioning(from = SceneB)).isFalse()
+        assertThat(state.isTransitioning(to = SceneB)).isTrue()
+        assertThat(state.isTransitioning(to = SceneA)).isFalse()
+        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
     }
 
     @Test
     fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
     }
 
     @Test
     fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val transition = state.setTargetScene(SceneB, coroutineScope = this)
         assertThat(transition).isNotNull()
         assertThat(state.transitionState).isEqualTo(transition)
 
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
-        assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
     @Test
     fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
 
         // Progress is 0f, so we don't animate at all and directly snap back to A.
-        assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+        assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
     }
 
     @Test
     fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        val state = MutableSceneTransitionLayoutState(SceneA)
 
         lateinit var transition: TransitionState.Transition
         val job =
             launch(start = CoroutineStart.UNDISPATCHED) {
-                transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!!
+                transition = state.setTargetScene(SceneB, coroutineScope = this)!!
             }
         assertThat(state.transitionState).isEqualTo(transition)
 
         // Cancelling the scope/job still sets the state to Idle(targetScene).
         job.cancel()
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+    }
+
+    private fun setupLinkedStates(
+        parentInitialScene: SceneKey = SceneC,
+        childInitialScene: SceneKey = SceneA,
+        sourceFrom: SceneKey? = SceneA,
+        sourceTo: SceneKey? = SceneB,
+        targetFrom: SceneKey? = SceneC,
+        targetTo: SceneKey = SceneD
+    ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+        val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
+        val link =
+            listOf(
+                StateLink(
+                    parentState,
+                    listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo))
+                )
+            )
+        val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
+        return Pair(
+            parentState as BaseSceneTransitionLayoutState,
+            childState as BaseSceneTransitionLayoutState
+        )
+    }
+
+    @Test
+    fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+    }
+
+    @Test
+    fun linkedTransition_transitiveLink() {
+        val parentParentState =
+            MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+        val parentLink =
+            listOf(
+                StateLink(
+                    parentParentState,
+                    listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC))
+                )
+            )
+        val parentState =
+            MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
+                as BaseSceneTransitionLayoutState
+        val link =
+            listOf(
+                StateLink(
+                    parentState,
+                    listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD))
+                )
+            )
+        val childState =
+            MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
+                as BaseSceneTransitionLayoutState
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+        assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+        assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_linkProgressIsEqual() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
+
+        childTransition.progress = .5f
+        assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
+    }
+
+    @Test
+    fun linkedTransition_reverseTransitionIsNotLinked() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneB, SceneA)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+        childState.startTransition(childTransition, null)
+
+        childState.finishTransition(childTransition, SceneA)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+        childState.startTransition(childTransition, null)
+
+        childState.finishTransition(childTransition, SceneD)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+        val parentTransition = TestableTransition(SceneC, SceneA)
+        childState.startTransition(childTransition, null)
+        parentState.startTransition(parentTransition, null)
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(parentTransition)
     }
 
     @Test
@@ -125,11 +277,11 @@
         val transitionkey = TransitionKey(debugName = "foo")
         val state =
             MutableSceneTransitionLayoutState(
-                TestScenes.SceneA,
+                SceneA,
                 transitions =
                     transitions {
-                        from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
-                        from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+                        from(SceneA, to = SceneB) { fade(TestElements.Foo) }
+                        from(SceneA, to = SceneB, key = transitionkey) {
                             fade(TestElements.Foo)
                             fade(TestElements.Bar)
                         }
@@ -138,19 +290,19 @@
                 as MutableSceneTransitionLayoutStateImpl
 
         // Default transition from A to B.
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
         assertThat(state.transformationSpec.transformations).hasSize(1)
 
         // Go back to A.
-        state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+        state.setTargetScene(SceneA, coroutineScope = this)
         testScheduler.advanceUntilIdle()
         assertThat(state.currentTransition).isNull()
-        assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(state.transitionState.currentScene).isEqualTo(SceneA)
 
         // Specific transition from A to B.
         assertThat(
                 state.setTargetScene(
-                    TestScenes.SceneB,
+                    SceneB,
                     coroutineScope = this,
                     transitionKey = transitionkey,
                 )
@@ -158,4 +310,83 @@
             .isNotNull()
         assertThat(state.transformationSpec.transformations).hasSize(2)
     }
+
+    @Test
+    fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        state.startTransition(
+            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+            transitionKey = null
+        )
+        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(TestScenes.SceneA))
+    }
+
+    @Test
+    fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        state.startTransition(
+            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+            transitionKey = null
+        )
+        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(TestScenes.SceneB))
+    }
+
+    @Test
+    fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+        val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+    }
+
+    @Test
+    fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+        val (parentState, childState) =
+            setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+        childState.finishTransition(childTransition, SceneA)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_fuzzyLinksAreNotMatched() {
+        val (parentState, childState) =
+            setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b2..0245cf2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.TestMonotonicFrameClock
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.withContext
 
@@ -12,16 +14,38 @@
  * function.
  *
  * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
  */
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
 fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
-    // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
-    withContext(TestMonotonicFrameClock(this)) {
-        MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+    val testScope: TestScope = this
+
+    withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+        val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+        val scope =
+            MonotonicClockTestScope(
+                testScope = testScopeWithMonotonicFrameClock,
+                testScheduler = testScope.testScheduler,
+                backgroundScope = backgroundScope,
+            )
+
+        // Run the test
+        scope.block()
     }
 }
 
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ *   in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
 class MonotonicClockTestScope(
-    coroutineScope: CoroutineScope,
-    val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+    testScope: CoroutineScope,
+    val testScheduler: TestCoroutineScheduler,
+    val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 81b5bd4..c399abc 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 001e3a5..54c7a08 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -49,7 +49,7 @@
  * existing lockscreen clock.
  */
 class DefaultClockController(
-    ctx: Context,
+    private val ctx: Context,
     private val layoutInflater: LayoutInflater,
     private val resources: Resources,
     private val settings: ClockSettings?,
@@ -121,7 +121,11 @@
         protected var targetRegion: Rect? = null
 
         override val config = ClockFaceConfig()
-        override val layout = DefaultClockFaceLayout(view)
+        override val layout =
+            DefaultClockFaceLayout(view).apply {
+                views[0].id =
+                    resources.getIdentifier("lockscreen_clock_view", "id", ctx.packageName)
+            }
 
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
             internal set
@@ -188,7 +192,11 @@
         seedColor: Int?,
         messageBuffer: MessageBuffer?,
     ) : DefaultClockFaceController(view, seedColor, messageBuffer) {
-        override val layout = DefaultClockFaceLayout(view)
+        override val layout =
+            DefaultClockFaceLayout(view).apply {
+                views[0].id =
+                    resources.getIdentifier("lockscreen_clock_view_large", "id", ctx.packageName)
+            }
         override val config =
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 4b21105..e39d7ed 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -20,8 +20,10 @@
 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
@@ -32,6 +34,12 @@
     private val backgroundDispatcher: CoroutineDispatcher,
     private val secureSettingsRepository: SecureSettingsRepository,
 ) {
+    val isNotificationHistoryEnabled: Flow<Boolean> =
+        secureSettingsRepository
+            .intSetting(name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED)
+            .map { it == 1 }
+            .distinctUntilChanged()
+
     /** The current state of the notification setting. */
     val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
         secureSettingsRepository
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 9ec6ec8..04e8090 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -23,6 +23,8 @@
 class NotificationSettingsInteractor(
     private val repository: NotificationSettingsRepository,
 ) {
+    val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled
+
     /** Should notifications be visible on the lockscreen? */
     val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
         repository.isShowNotificationsOnLockScreenEnabled
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 754d5dc..2a87452 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -27,7 +27,11 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.withContext
 
-/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+/**
+ * Defines interface for classes that can provide access to data from [Settings.Secure].
+ * This repository doesn't guarantee to provide value across different users. For that
+ * see: [UserAwareSecureSettingsRepository]
+ */
 interface SecureSettingsRepository {
 
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
index 2be22a6..2f1d354 100644
--- a/packages/SystemUI/log/Android.bp
+++ b/packages/SystemUI/log/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
index 507ea25..98f7ace 100644
--- a/packages/SystemUI/monet/Android.bp
+++ b/packages/SystemUI/monet/Android.bp
@@ -14,6 +14,7 @@
 // limitations under the License.
 //
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1519021..c6327ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,11 +24,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -90,6 +92,8 @@
         whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
             .thenReturn(mock(ImageView::class.java))
         `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+
+        val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
         val fakeFeatureFlags = FakeFeatureFlags()
         fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
         mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
@@ -111,6 +115,7 @@
                 postureController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
+                keyguardKeyboardInteractor,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c82688c..e8a43ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,6 +36,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
@@ -51,8 +52,9 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.SessionTracker
@@ -202,6 +204,7 @@
         whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
 
+        val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
         featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
@@ -232,13 +235,12 @@
                 postureController,
                 featureFlags,
                 mSelectedUserInteractor,
+                keyguardKeyboardInteractor,
             )
 
         kosmos = testKosmos()
         sceneInteractor = kosmos.sceneInteractor
-        keyguardTransitionInteractor =
-            KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
-                .keyguardTransitionInteractor
+        keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index b6605ed..fa47a02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -59,15 +59,21 @@
     }
 
     @Test
+    fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+            runCurrent()
+
+            Truth.assertThat(actualValue).isFalse()
+        }
+
+    @Test
     fun isEnabled_initiallyGetsSettingsValue() =
         scope.runTest {
             val actualValue by collectLastValue(underTest.isEnabled(testUser1))
 
-            settings.putIntForUser(
-                SETTING_NAME,
-                ENABLED,
-                testUser1.identifier
-            )
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
             runCurrent()
 
             Truth.assertThat(actualValue).isTrue()
@@ -78,25 +84,13 @@
         scope.runTest {
             val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
 
-            settings.putIntForUser(
-                SETTING_NAME,
-                DISABLED,
-                testUser1.identifier
-            )
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
             runCurrent()
 
-            settings.putIntForUser(
-                SETTING_NAME,
-                ENABLED,
-                testUser1.identifier
-            )
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
             runCurrent()
 
-            settings.putIntForUser(
-                SETTING_NAME,
-                DISABLED,
-                testUser1.identifier
-            )
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
             runCurrent()
 
             Truth.assertThat(flowValues.size).isEqualTo(3)
@@ -109,26 +103,14 @@
             val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
             val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
 
-            settings.putIntForUser(
-                SETTING_NAME,
-                DISABLED,
-                testUser1.identifier
-            )
-            settings.putIntForUser(
-                SETTING_NAME,
-                DISABLED,
-                testUser2.identifier
-            )
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
             runCurrent()
 
             Truth.assertThat(lastValueUser1).isFalse()
             Truth.assertThat(lastValueUser2).isFalse()
 
-            settings.putIntForUser(
-                SETTING_NAME,
-                ENABLED,
-                testUser1.identifier
-            )
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
             runCurrent()
 
             Truth.assertThat(lastValueUser1).isTrue()
@@ -142,11 +124,7 @@
             runCurrent()
             Truth.assertThat(success).isTrue()
 
-            val actualValue =
-                settings.getIntForUser(
-                    SETTING_NAME,
-                    testUser1.identifier
-                )
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
             Truth.assertThat(actualValue).isEqualTo(ENABLED)
         }
 
@@ -157,11 +135,7 @@
             runCurrent()
             Truth.assertThat(success).isTrue()
 
-            val actualValue =
-                settings.getIntForUser(
-                    SETTING_NAME,
-                    testUser1.identifier
-                )
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
             Truth.assertThat(actualValue).isEqualTo(DISABLED)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 30eb782..3d8159e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -59,6 +60,16 @@
     }
 
     @Test
+    fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+            runCurrent()
+
+            Truth.assertThat(actualValue).isFalse()
+        }
+
+    @Test
     fun isEnabled_initiallyGetsSettingsValue() =
         scope.runTest {
             val actualValue by collectLastValue(underTest.isEnabled(testUser1))
@@ -72,8 +83,7 @@
     @Test
     fun isEnabled_settingUpdated_valueUpdated() =
         scope.runTest {
-            val flowValues: List<Boolean> by
-                collectValues(underTest.isEnabled(testUser1))
+            val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
 
             settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 0e257bc..55cfcc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -16,44 +16,27 @@
 
 package com.android.systemui.biometrics
 
-import android.os.Handler
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertFalse
@@ -64,9 +47,7 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -77,70 +58,24 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest :
     UdfpsKeyguardViewLegacyControllerBaseTest() {
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
-    private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
-    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
-
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    private val keyguardBouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
     @Before
     override fun setUp() {
         allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
         MockitoAnnotations.initMocks(this)
-        keyguardBouncerRepository =
-            KeyguardBouncerRepositoryImpl(
-                FakeSystemClock(),
-                testScope.backgroundScope,
-                bouncerLogger,
-            )
-        transitionRepository = FakeKeyguardTransitionRepository()
         super.setUp()
     }
 
     override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy {
-        mPrimaryBouncerInteractor =
-            PrimaryBouncerInteractor(
-                keyguardBouncerRepository,
-                mock(BouncerView::class.java),
-                mock(Handler::class.java),
-                mKeyguardStateController,
-                mock(KeyguardSecurityModel::class.java),
-                mock(PrimaryBouncerCallbackInteractor::class.java),
-                mock(FalsingCollector::class.java),
-                mock(DismissCallbackRegistry::class.java),
-                context,
-                mKeyguardUpdateMonitor,
-                FakeTrustRepository(),
-                testScope.backgroundScope,
-                mSelectedUserInteractor,
-                mock(DeviceEntryFaceAuthInteractor::class.java),
-            )
-        mAlternateBouncerInteractor =
-            AlternateBouncerInteractor(
-                mock(StatusBarStateController::class.java),
-                mock(KeyguardStateController::class.java),
-                keyguardBouncerRepository,
-                FakeFingerprintPropertyRepository(),
-                mock(BiometricSettingsRepository::class.java),
-                mock(SystemClock::class.java),
-                mKeyguardUpdateMonitor,
-                testScope.backgroundScope,
-            )
-        mKeyguardTransitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = testScope.backgroundScope,
-                    repository = transitionRepository,
-                )
-                .keyguardTransitionInteractor
-        mUdfpsOverlayInteractor =
-            UdfpsOverlayInteractor(
-                context,
-                mock(AuthController::class.java),
-                mock(SelectedUserInteractor::class.java),
-                testScope.backgroundScope,
-            )
+        mPrimaryBouncerInteractor = kosmos.primaryBouncerInteractor
+        mAlternateBouncerInteractor = kosmos.alternateBouncerInteractor
+        mKeyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+        mUdfpsOverlayInteractor = kosmos.udfpsOverlayInteractor
         return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
index 6380ace..2386957 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
@@ -23,7 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -32,6 +32,7 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -67,7 +68,8 @@
                         scope = applicationCoroutineScope,
                         context = context,
                         bgHandler = handler,
-                        logger = PackageUpdateLogger(logcatLogBuffer())
+                        logger = PackageUpdateLogger(logcatLogBuffer()),
+                        systemClock = fakeSystemClock,
                     )
                 updateMonitor
             }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
index d610925..35d9d3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
@@ -23,7 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -32,6 +32,7 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -71,8 +72,11 @@
                     bgHandler = handler,
                     context = context,
                     scope = applicationCoroutineScope,
-                    logger = PackageUpdateLogger(logcatLogBuffer())
+                    logger = PackageUpdateLogger(logcatLogBuffer()),
+                    systemClock = fakeSystemClock,
                 )
+
+            fakeSystemClock.setCurrentTimeMillis(0)
         }
 
     @Test
@@ -96,11 +100,16 @@
                 val packageChange by collectLastValue(monitor.packageChanged)
                 assertThat(packageChange).isNull()
 
+                fakeSystemClock.setCurrentTimeMillis(100)
                 monitor.onPackageAdded(TEST_PACKAGE, 123)
 
                 assertThat(packageChange)
                     .isEqualTo(
-                        PackageChangeModel.Installed(packageName = TEST_PACKAGE, packageUid = 123)
+                        PackageChangeModel.Installed(
+                            packageName = TEST_PACKAGE,
+                            packageUid = 123,
+                            timeMillis = 100,
+                        )
                     )
             }
         }
@@ -112,11 +121,16 @@
                 val packageChange by collectLastValue(monitor.packageChanged)
                 assertThat(packageChange).isNull()
 
+                fakeSystemClock.setCurrentTimeMillis(200)
                 monitor.onPackageRemoved(TEST_PACKAGE, 123)
 
                 assertThat(packageChange)
                     .isEqualTo(
-                        PackageChangeModel.Uninstalled(packageName = TEST_PACKAGE, packageUid = 123)
+                        PackageChangeModel.Uninstalled(
+                            packageName = TEST_PACKAGE,
+                            packageUid = 123,
+                            timeMillis = 200,
+                        )
                     )
             }
         }
@@ -128,11 +142,16 @@
                 val packageChange by collectLastValue(monitor.packageChanged)
                 assertThat(packageChange).isNull()
 
+                fakeSystemClock.setCurrentTimeMillis(100)
                 monitor.onPackageChanged(TEST_PACKAGE, 123, emptyArray())
 
                 assertThat(packageChange)
                     .isEqualTo(
-                        PackageChangeModel.Changed(packageName = TEST_PACKAGE, packageUid = 123)
+                        PackageChangeModel.Changed(
+                            packageName = TEST_PACKAGE,
+                            packageUid = 123,
+                            timeMillis = 100,
+                        )
                     )
             }
         }
@@ -144,13 +163,15 @@
                 val packageChange by collectLastValue(monitor.packageChanged)
                 assertThat(packageChange).isNull()
 
+                fakeSystemClock.setCurrentTimeMillis(100)
                 monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)
 
                 assertThat(packageChange)
                     .isEqualTo(
                         PackageChangeModel.UpdateStarted(
                             packageName = TEST_PACKAGE,
-                            packageUid = 123
+                            packageUid = 123,
+                            timeMillis = 100,
                         )
                     )
             }
@@ -163,13 +184,15 @@
                 val packageChange by collectLastValue(monitor.packageChanged)
                 assertThat(packageChange).isNull()
 
+                fakeSystemClock.setCurrentTimeMillis(100)
                 monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)
 
                 assertThat(packageChange)
                     .isEqualTo(
                         PackageChangeModel.UpdateFinished(
                             packageName = TEST_PACKAGE,
-                            packageUid = 123
+                            packageUid = 123,
+                            timeMillis = 100,
                         )
                     )
             }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/PackageChangeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/PackageChangeInteractorTest.kt
new file mode 100644
index 0000000..a164e7c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/PackageChangeInteractorTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.domain.interactor
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.data.repository.packageChangeRepository
+import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageChangeInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: PackageChangeInteractor
+
+    @Before
+    fun setUp() =
+        with(kosmos) {
+            underTest =
+                PackageChangeInteractor(
+                    packageChangeRepository = packageChangeRepository,
+                    userInteractor = selectedUserInteractor,
+                )
+            fakeUserRepository.setUserInfos(listOf(MAIN_USER, SECONDARY_USER))
+        }
+
+    @Test
+    fun packageChanges() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(underTest.packageChanged(MAIN_USER_HANDLE))
+                assertThat(packageChange).isNull()
+
+                // Even if secondary user is active, we should still receive changes for the
+                // primary user.
+                setUser(SECONDARY_USER)
+
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "pkg",
+                        packageUid = UserHandle.getUid(MAIN_USER.id, /* appId = */ 10),
+                    )
+                )
+
+                assertThat(packageChange).isInstanceOf(PackageChangeModel.Installed::class.java)
+                assertThat(packageChange?.packageName).isEqualTo("pkg")
+            }
+        }
+
+    @Test
+    fun packageChanges_ignoresUpdatesFromOtherUsers() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(underTest.packageChanged(MAIN_USER_HANDLE))
+                assertThat(packageChange).isNull()
+
+                setUser(SECONDARY_USER)
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "pkg",
+                        packageUid = UserHandle.getUid(SECONDARY_USER.id, /* appId = */ 10),
+                    )
+                )
+
+                assertThat(packageChange).isNull()
+            }
+        }
+
+    @Test
+    fun packageChanges_forCurrentUser() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChanges by collectValues(underTest.packageChanged(UserHandle.CURRENT))
+                assertThat(packageChanges).isEmpty()
+
+                setUser(SECONDARY_USER)
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "first",
+                        packageUid = UserHandle.getUid(SECONDARY_USER.id, /* appId = */ 10),
+                    )
+                )
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "second",
+                        packageUid = UserHandle.getUid(MAIN_USER.id, /* appId = */ 10),
+                    )
+                )
+                setUser(MAIN_USER)
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "third",
+                        packageUid = UserHandle.getUid(MAIN_USER.id, /* appId = */ 10),
+                    )
+                )
+
+                assertThat(packageChanges.map { it.packageName })
+                    .containsExactly("first", "third")
+                    .inOrder()
+            }
+        }
+
+    @Test
+    fun packageChanges_forSpecificPackageName() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by
+                    collectLastValue(underTest.packageChanged(MAIN_USER_HANDLE, "mypkg"))
+                assertThat(packageChange).isNull()
+
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "other",
+                        packageUid = UserHandle.getUid(MAIN_USER.id, /* appId = */ 10),
+                    )
+                )
+                assertThat(packageChange).isNull()
+
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Installed(
+                        packageName = "mypkg",
+                        packageUid = UserHandle.getUid(MAIN_USER.id, /* appId = */ 10),
+                    )
+                )
+                assertThat(packageChange).isInstanceOf(PackageChangeModel.Installed::class.java)
+                assertThat(packageChange?.packageName).isEqualTo("mypkg")
+            }
+        }
+
+    private suspend fun TestScope.setUser(user: UserInfo) {
+        kosmos.fakeUserRepository.setSelectedUserInfo(user)
+        runCurrent()
+    }
+
+    private companion object {
+        val MAIN_USER_HANDLE = UserHandle.of(1)
+        val MAIN_USER = UserInfo(MAIN_USER_HANDLE.identifier, "main", UserInfo.FLAG_MAIN)
+        val SECONDARY_USER_HANDLE = UserHandle.of(2)
+        val SECONDARY_USER = UserInfo(SECONDARY_USER_HANDLE.identifier, "secondary", 0)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 76b0d4a..45f98be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -44,78 +45,79 @@
 class CommunalMediaRepositoryImplTest : SysuiTestCase() {
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var mediaData: MediaData
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+    private lateinit var underTest: CommunalMediaRepositoryImpl
 
     private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
         KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
     }
 
-    private lateinit var mediaRepository: CommunalMediaRepository
-
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        underTest =
+            CommunalMediaRepositoryImpl(
+                mediaDataManager,
+                tableLogBuffer,
+            )
     }
 
     @Test
     fun hasAnyMediaOrRecommendation_defaultsToFalse() =
         testScope.runTest {
-            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
-            val mediaModel = collectLastValue(mediaRepository.mediaModel)
+            val mediaModel = collectLastValue(underTest.mediaModel)
             runCurrent()
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
         }
 
     @Test
     fun mediaModel_updatesWhenMediaDataLoaded() =
         testScope.runTest {
-            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
             // Listener is added
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
 
             // Initial value is false.
-            val mediaModel = collectLastValue(mediaRepository.mediaModel)
+            val mediaModel = collectLastValue(underTest.mediaModel)
             runCurrent()
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
 
             // Change to media available and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
             assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
         }
 
     @Test
     fun mediaModel_updatesWhenMediaDataRemoved() =
         testScope.runTest {
-            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
             // Listener is added
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
 
             // Change to media available and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
-            val mediaModel = collectLastValue(mediaRepository.mediaModel)
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            val mediaModel = collectLastValue(underTest.mediaModel)
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
 
             // Change to media unavailable and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
             mediaDataListenerCaptor.value.onMediaDataRemoved("key")
             runCurrent()
 
             // Media active now returns false.
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index d15e15e..6bff0dc7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -36,11 +38,15 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @android.platform.test.annotations.EnabledOnRavenwood
 class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
     private lateinit var underTest: CommunalPrefsRepositoryImpl
 
     private val kosmos = testKosmos()
@@ -51,6 +57,8 @@
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
         userRepository = kosmos.fakeUserRepository
         userRepository.setUserInfos(USER_INFOS)
 
@@ -67,6 +75,8 @@
                 kosmos.testDispatcher,
                 userRepository,
                 userFileManager,
+                logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+                tableLogBuffer,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index 0c66bbb..2911a50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.settings.FakeSettings
@@ -34,12 +35,15 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @android.platform.test.annotations.EnabledOnRavenwood
 class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
@@ -64,6 +68,7 @@
                 userRepository,
                 secureSettings,
                 logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+                tableLogBuffer,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c979ca6..475179d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -198,13 +198,22 @@
         }
 
     @Test
-    fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+    fun deleteWidgetFromDb() =
         testScope.runTest {
             val id = 1
-            underTest.deleteWidget(id)
+            underTest.deleteWidgetFromDb(id)
             runCurrent()
 
             verify(communalWidgetDao).deleteWidgetById(id)
+        }
+
+    @Test
+    fun deleteWidgetFromHost() =
+        testScope.runTest {
+            val id = 1
+            underTest.deleteWidgetFromHost(id)
+            runCurrent()
+
             verify(appWidgetHost).deleteAppWidgetId(id)
         }
 
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 ee01bf9..c5485c5 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
@@ -642,6 +642,53 @@
         }
 
     @Test
+    fun isCommunalVisible() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+                )
+            communalRepository.setTransitionState(transitionState)
+
+            // isCommunalVisible is false when not on communal.
+            val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
+            assertThat(isCommunalVisible).isEqualTo(false)
+
+            // Start transition to communal.
+            transitionState.value =
+                ObservableCommunalTransitionState.Transition(
+                    fromScene = CommunalSceneKey.Blank,
+                    toScene = CommunalSceneKey.Communal,
+                    progress = flowOf(0f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+
+            // isCommunalVisible is true once transition starts.
+            assertThat(isCommunalVisible).isEqualTo(true)
+
+            // Finish transition to communal
+            transitionState.value =
+                ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+
+            // isCommunalVisible is true since we're on communal.
+            assertThat(isCommunalVisible).isEqualTo(true)
+
+            // Start transition away from communal.
+            transitionState.value =
+                ObservableCommunalTransitionState.Transition(
+                    fromScene = CommunalSceneKey.Communal,
+                    toScene = CommunalSceneKey.Blank,
+                    progress = flowOf(1.0f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+
+            // isCommunalVisible is still true as the false as soon as transition away runs.
+            assertThat(isCommunalVisible).isEqualTo(true)
+        }
+
+    @Test
     fun testShowWidgetEditorStartsActivity() =
         testScope.runTest {
             underTest.showWidgetEditor()
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 273d1cd..cf727cf 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
@@ -36,6 +36,7 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
@@ -82,6 +83,7 @@
                 kosmos.communalInteractor,
                 mediaHost,
                 uiEventLogger,
+                logcatLogBuffer("CommunalEditModeViewModelTest"),
             )
     }
 
@@ -142,6 +144,49 @@
         }
 
     @Test
+    fun deleteWidget() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Widgets available.
+            val widgets =
+                listOf(
+                    CommunalWidgetContentModel(
+                        appWidgetId = 0,
+                        priority = 30,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 1,
+                        priority = 20,
+                        providerInfo = mock(),
+                    ),
+                )
+            widgetRepository.setCommunalWidgets(widgets)
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            // Widgets and CTA tile are shown.
+            assertThat(communalContent?.size).isEqualTo(3)
+            assertThat(communalContent?.get(0))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(1))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(2))
+                .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+
+            underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+
+            // Only one widget and CTA tile remain.
+            assertThat(communalContent?.size).isEqualTo(2)
+            val item = communalContent?.get(0)
+            val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+            assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+            assertThat(communalContent?.get(1))
+                .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+        }
+
+    @Test
     fun reorderWidget_uiEventLogging_start() {
         underTest.onReorderWidgetStart()
         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 0723e83..73d3091 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
@@ -40,6 +40,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
@@ -99,6 +100,7 @@
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 mediaHost,
+                logcatLogBuffer("CommunalViewModelTest"),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6808f5d..b611e0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -37,16 +37,11 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -59,52 +54,46 @@
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
-import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.BiometricType
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -129,6 +118,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos().apply { this.commandQueue = this.fakeCommandQueue }
     private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
 
     @Mock private lateinit var faceManager: FaceManager
@@ -136,7 +126,6 @@
     @Mock private lateinit var sessionTracker: SessionTracker
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     @Captor
     private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -151,11 +140,11 @@
 
     @Captor
     private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
-    private lateinit var testDispatcher: TestDispatcher
+    private val testDispatcher = kosmos.testDispatcher
 
-    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var testScope: TestScope
-    private lateinit var fakeUserRepository: FakeUserRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val testScope = kosmos.testScope
+    private val fakeUserRepository = kosmos.fakeUserRepository
     private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
@@ -163,88 +152,30 @@
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var deviceEntryFingerprintAuthRepository:
-        FakeDeviceEntryFingerprintAuthRepository
-    private lateinit var trustRepository: FakeTrustRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var powerRepository: FakePowerRepository
-    private lateinit var powerInteractor: PowerInteractor
-    private lateinit var keyguardInteractor: KeyguardInteractor
-    private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
-    private lateinit var displayRepository: FakeDisplayRepository
-    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
+    private val deviceEntryFingerprintAuthRepository =
+        kosmos.fakeDeviceEntryFingerprintAuthRepository
+    private val trustRepository = kosmos.fakeTrustRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val powerInteractor = kosmos.powerInteractor
+    private val keyguardInteractor = kosmos.keyguardInteractor
+    private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
+    private val displayStateInteractor = kosmos.displayStateInteractor
+    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val displayRepository = kosmos.displayRepository
+    private val fakeCommandQueue = kosmos.fakeCommandQueue
+    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
     private lateinit var featureFlags: FakeFeatureFlags
-    private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
 
     private var wasAuthCancelled = false
     private var wasDetectCancelled = false
 
-    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
-
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        testDispatcher = StandardTestDispatcher()
-        testScope = TestScope(testDispatcher)
-        fakeUserRepository = FakeUserRepository()
         fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
-        trustRepository = FakeTrustRepository()
         featureFlags = FakeFeatureFlags()
 
-        powerRepository = FakePowerRepository()
-        powerInteractor =
-            PowerInteractorFactory.create(
-                    repository = powerRepository,
-                )
-                .powerInteractor
-
-        val withDeps =
-            KeyguardInteractorFactory.create(
-                featureFlags = featureFlags,
-                powerInteractor = powerInteractor,
-            )
-        keyguardInteractor = withDeps.keyguardInteractor
-        keyguardRepository = withDeps.repository
-        bouncerRepository = withDeps.bouncerRepository
-
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-        keyguardTransitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = testScope.backgroundScope,
-                    repository = keyguardTransitionRepository,
-                    keyguardInteractor = keyguardInteractor,
-                )
-                .keyguardTransitionInteractor
-
-        fakeCommandQueue = withDeps.commandQueue
-
-        alternateBouncerInteractor =
-            AlternateBouncerInteractor(
-                bouncerRepository = bouncerRepository,
-                fingerprintPropertyRepository = FakeFingerprintPropertyRepository(),
-                biometricSettingsRepository = biometricSettingsRepository,
-                systemClock = mock(SystemClock::class.java),
-                keyguardStateController = FakeKeyguardStateController(),
-                statusBarStateController = mock(StatusBarStateController::class.java),
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                scope = testScope.backgroundScope,
-            )
-
-        displayRepository = FakeDisplayRepository()
-        displayStateInteractor =
-            DisplayStateInteractorImpl(
-                applicationScope = testScope.backgroundScope,
-                context = context,
-                mainExecutor = FakeExecutor(FakeSystemClock()),
-                displayStateRepository = FakeDisplayStateRepository(),
-                displayRepository = displayRepository,
-            )
-
         bypassStateChangedListener =
             KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
         whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
@@ -282,7 +213,6 @@
                 testScope.backgroundScope
             )
 
-        fakeFacePropertyRepository = FakeFacePropertyRepository()
         return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
index efccf7a..2c890f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -15,27 +15,32 @@
  */
 package com.android.systemui.dreams.homecontrols
 
+import android.service.dream.dreamManager
+import com.android.systemui.common.domain.interactor.packageChangeInteractor
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.fakeSystemClock
 
 val Kosmos.homeControlsComponentInteractor by
     Kosmos.Fixture {
         HomeControlsComponentInteractor(
             selectedComponentRepository = selectedComponentRepository,
-            controlsComponent,
+            controlsComponent = controlsComponent,
             authorizedPanelsRepository = authorizedPanelsRepository,
             userRepository = fakeUserRepository,
             bgScope = applicationCoroutineScope,
+            systemClock = fakeSystemClock,
+            dreamManager = dreamManager,
+            packageChangeInteractor = packageChangeInteractor,
         )
     }
 
 val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
 val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
-val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index ce74a90..298ce70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -20,84 +20,70 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.ServiceInfo
 import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
 import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class HomeControlsComponentInteractorTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
 
-    private lateinit var controlsComponent: ControlsComponent
-    private lateinit var controlsListingController: ControlsListingController
-    private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
     private lateinit var underTest: HomeControlsComponentInteractor
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
 
     @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        userRepository = kosmos.fakeUserRepository
-        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+    fun setUp() =
+        with(kosmos) {
+            fakeSystemClock.setCurrentTimeMillis(0)
+            fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+            whenever(controlsComponent.getControlsListingController())
+                .thenReturn(Optional.of(controlsListingController))
 
-        controlsComponent = kosmos.controlsComponent
-        authorizedPanelsRepository = kosmos.authorizedPanelsRepository
-        controlsListingController = kosmos.controlsListingController
-        selectedComponentRepository = kosmos.selectedComponentRepository
-
-        selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
-        whenever(controlsComponent.getControlsListingController())
-            .thenReturn(Optional.of(controlsListingController))
-
-        underTest =
-            HomeControlsComponentInteractor(
-                selectedComponentRepository,
-                controlsComponent,
-                authorizedPanelsRepository,
-                userRepository,
-                kosmos.applicationCoroutineScope,
-            )
-    }
+            underTest = homeControlsComponentInteractor
+        }
 
     @Test
     fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
                 runServicesUpdate()
-                assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+                assertThat(actualValue).isEqualTo(TEST_COMPONENT)
             }
         }
 
@@ -105,16 +91,15 @@
     fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 whenever(controlsListingController.getCurrentServices())
                     .thenReturn(
-                        listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+                        listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
                     )
                 val actualValue by collectLastValue(underTest.panelComponent)
-                assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+                assertThat(actualValue).isEqualTo(TEST_COMPONENT)
             }
         }
 
@@ -122,9 +107,8 @@
     fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
@@ -137,8 +121,8 @@
     fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
@@ -151,17 +135,24 @@
     fun testPanelComponentReturnsComponentNameForDifferentUsers() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(ANOTHER_USER)
+                val actualValue by collectLastValue(underTest.panelComponent)
+
+                // Secondary user has non-panel selected.
+                setActiveUser(ANOTHER_USER)
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
-                selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+
+                // Primary user has panel selected.
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
 
-                val actualValue by collectLastValue(underTest.panelComponent)
-                assertThat(actualValue).isNull()
                 runServicesUpdate()
-                assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+                assertThat(actualValue).isEqualTo(TEST_COMPONENT)
+
+                // Back to secondary user, should be null.
+                setActiveUser(ANOTHER_USER)
+                runServicesUpdate()
+                assertThat(actualValue).isNull()
             }
         }
 
@@ -169,24 +160,119 @@
     fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 whenever(controlsComponent.getControlsListingController())
                     .thenReturn(Optional.empty())
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
             }
         }
 
+    @Test
+    fun testMonitoringUpdatesAndRestart() =
+        with(kosmos) {
+            testScope.runTest {
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+                selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+                whenever(controlsListingController.getCurrentServices())
+                    .thenReturn(
+                        listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+                    )
+
+                val job = launch { underTest.monitorUpdatesAndRestart() }
+                val panelComponent by collectLastValue(underTest.panelComponent)
+
+                assertThat(panelComponent).isEqualTo(TEST_COMPONENT)
+                verify(dreamManager, never()).startDream()
+
+                fakeSystemClock.advanceTime(100)
+                // The package update is started.
+                fakePackageChangeRepository.notifyUpdateStarted(
+                    TEST_PACKAGE,
+                    UserHandle.of(PRIMARY_USER_ID),
+                )
+                fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds)
+                // Task fragment becomes empty as a result of the update.
+                underTest.onTaskFragmentEmpty()
+
+                runCurrent()
+                verify(dreamManager, never()).startDream()
+
+                fakeSystemClock.advanceTime(500)
+                // The package update is finished.
+                fakePackageChangeRepository.notifyUpdateFinished(
+                    TEST_PACKAGE,
+                    UserHandle.of(PRIMARY_USER_ID),
+                )
+
+                runCurrent()
+                verify(dreamManager).startDream()
+                job.cancel()
+            }
+        }
+
+    @Test
+    fun testMonitoringUpdatesAndRestart_dreamEndsAfterDelay() =
+        with(kosmos) {
+            testScope.runTest {
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+                selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+                whenever(controlsListingController.getCurrentServices())
+                    .thenReturn(
+                        listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+                    )
+
+                val job = launch { underTest.monitorUpdatesAndRestart() }
+                val panelComponent by collectLastValue(underTest.panelComponent)
+
+                assertThat(panelComponent).isEqualTo(TEST_COMPONENT)
+                verify(dreamManager, never()).startDream()
+
+                fakeSystemClock.advanceTime(100)
+                // The package update is started.
+                fakePackageChangeRepository.notifyUpdateStarted(
+                    TEST_PACKAGE,
+                    UserHandle.of(PRIMARY_USER_ID),
+                )
+                fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds + 100)
+                // Task fragment becomes empty as a result of the update.
+                underTest.onTaskFragmentEmpty()
+
+                runCurrent()
+                verify(dreamManager, never()).startDream()
+
+                fakeSystemClock.advanceTime(500)
+                // The package update is finished.
+                fakePackageChangeRepository.notifyUpdateFinished(
+                    TEST_PACKAGE,
+                    UserHandle.of(PRIMARY_USER_ID),
+                )
+
+                runCurrent()
+                verify(dreamManager, never()).startDream()
+                job.cancel()
+            }
+        }
+
     private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
         val listings =
-            listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
-        val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
+            listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
+        val callback = withArgCaptor {
+            verify(kosmos.controlsListingController).addCallback(capture())
+        }
         callback.onServicesUpdated(listings)
     }
 
+    private suspend fun TestScope.setActiveUser(user: UserInfo) {
+        kosmos.fakeUserRepository.setSelectedUserInfo(user)
+        kosmos.fakeUserTracker.set(listOf(user), 0)
+        runCurrent()
+    }
+
     private fun ControlsServiceInfo(
         componentName: ComponentName,
         label: CharSequence,
@@ -237,19 +323,9 @@
             )
         private const val TEST_PACKAGE = "pkg"
         private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
-        private const val TEST_PACKAGE_PANEL = "pkg.panel"
-        private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
         private val TEST_SELECTED_COMPONENT_PANEL =
-            SelectedComponentRepository.SelectedComponent(
-                TEST_PACKAGE_PANEL,
-                TEST_COMPONENT_PANEL,
-                true
-            )
+            SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, true)
         private val TEST_SELECTED_COMPONENT_NON_PANEL =
-            SelectedComponentRepository.SelectedComponent(
-                TEST_PACKAGE_PANEL,
-                TEST_COMPONENT_PANEL,
-                false
-            )
+            SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, false)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index d28b6bf..0a3aea7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -16,20 +16,19 @@
 package com.android.systemui.dreams.homecontrols
 
 import android.app.Activity
-import android.content.ComponentName
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import java.util.Optional
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,78 +42,60 @@
 class HomeControlsDreamServiceTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
-    private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
     @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
     @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
     @Mock private lateinit var activity: Activity
-    private val logBuffer: LogBuffer = create()
 
     private lateinit var underTest: HomeControlsDreamService
-    private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
-    private lateinit var fakeDreamActivityProvider: DreamActivityProvider
-    private lateinit var controlsComponent: ControlsComponent
-    private lateinit var controlsListingController: ControlsListingController
 
     @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
-            .thenReturn(taskFragmentComponent)
+    fun setup() =
+        with(kosmos) {
+            MockitoAnnotations.initMocks(this@HomeControlsDreamServiceTest)
+            whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
+                .thenReturn(taskFragmentComponent)
 
-        controlsSettingsRepository = FakeControlsSettingsRepository()
-        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+            whenever(controlsComponent.getControlsListingController())
+                .thenReturn(Optional.of(controlsListingController))
 
-        controlsComponent = kosmos.controlsComponent
-        controlsListingController = kosmos.controlsListingController
+            underTest = buildService { activity }
+        }
 
-        whenever(controlsComponent.getControlsListingController())
-            .thenReturn(Optional.of(controlsListingController))
+    @Test
+    fun testOnAttachedToWindowCreatesTaskFragmentComponent() =
+        testScope.runTest {
+            underTest.onAttachedToWindow()
+            verify(taskFragmentComponentFactory).create(any(), any(), any(), any())
+        }
 
-        homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
+    @Test
+    fun testOnDetachedFromWindowDestroyTaskFragmentComponent() =
+        testScope.runTest {
+            underTest.onAttachedToWindow()
+            underTest.onDetachedFromWindow()
+            verify(taskFragmentComponent).destroy()
+        }
 
-        fakeDreamActivityProvider = DreamActivityProvider { activity }
-        underTest =
-            HomeControlsDreamService(
-                controlsSettingsRepository,
-                taskFragmentComponentFactory,
-                homeControlsComponentInteractor,
-                fakeDreamActivityProvider,
-                logBuffer
+    @Test
+    fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() =
+        testScope.runTest {
+            underTest = buildService { null }
+
+            underTest.onAttachedToWindow()
+            verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
+        }
+
+    private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
+        with(kosmos) {
+            return HomeControlsDreamService(
+                controlsSettingsRepository = FakeControlsSettingsRepository(),
+                taskFragmentFactory = taskFragmentComponentFactory,
+                homeControlsComponentInteractor = homeControlsComponentInteractor,
+                dreamActivityProvider = activityProvider,
+                bgDispatcher = testDispatcher,
+                logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
             )
-    }
-
-    @Test
-    fun testOnAttachedToWindowCreatesTaskFragmentComponent() {
-        underTest.onAttachedToWindow()
-        verify(taskFragmentComponentFactory).create(any(), any(), any(), any())
-    }
-
-    @Test
-    fun testOnDetachedFromWindowDestroyTaskFragmentComponent() {
-        underTest.onAttachedToWindow()
-        underTest.onDetachedFromWindow()
-        verify(taskFragmentComponent).destroy()
-    }
-
-    @Test
-    fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() {
-        fakeDreamActivityProvider = DreamActivityProvider { null }
-        underTest =
-            HomeControlsDreamService(
-                controlsSettingsRepository,
-                taskFragmentComponentFactory,
-                homeControlsComponentInteractor,
-                fakeDreamActivityProvider,
-                logBuffer
-            )
-
-        underTest.onAttachedToWindow()
-        verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
-    }
-
-    companion object {
-        private const val TEST_PACKAGE_PANEL = "pkg.panel"
-        private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
-    }
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 6610e70..87b1bbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,8 +85,7 @@
 
         userRepository.setUserInfos(listOf(PRIMARY_USER))
 
-        whenever(authorizedPanelsRepository.getAuthorizedPanels())
-            .thenReturn(setOf(TEST_PACKAGE_PANEL))
+        authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
 
         whenever(controlsComponent.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 74c1970..2a9bc4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -28,7 +28,6 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import org.junit.Before;
@@ -47,8 +46,6 @@
     @Mock
     CentralSurfaces mCentralSurfaces;
     @Mock
-    NotificationShadeWindowController mNotificationShadeWindowController;
-    @Mock
     DreamTouchHandler.TouchSession mTouchSession;
     CommunalTouchHandler mTouchHandler;
 
@@ -59,17 +56,10 @@
         MockitoAnnotations.initMocks(this);
         mTouchHandler = new CommunalTouchHandler(
                 Optional.of(mCentralSurfaces),
-                mNotificationShadeWindowController,
                 INITIATION_WIDTH);
     }
 
     @Test
-    public void testSessionStartForcesShadeOpen() {
-        mTouchHandler.onSessionStart(mTouchSession);
-        verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
-    }
-
-    @Test
     public void testEventPropagation() {
         final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 98f0211..9d34903 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -18,23 +18,27 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Intent
+import android.view.accessibility.accessibilityManagerWrapper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.uiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -43,7 +47,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -51,27 +54,25 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardLongPressInteractorTest : SysuiTestCase() {
-
-    @Mock private lateinit var logger: UiEventLogger
-    @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
+    private val kosmos =
+        testKosmos().apply {
+            this.accessibilityManagerWrapper = mock<AccessibilityManagerWrapper>()
+            this.uiEventLogger = mock<UiEventLoggerFake>()
+        }
 
     private lateinit var underTest: KeyguardLongPressInteractor
 
-    private lateinit var testScope: TestScope
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private val logger = kosmos.uiEventLogger
+    private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true)
-        whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
-            it.arguments[0]
-        }
-
-        testScope = TestScope()
-        keyguardRepository = FakeKeyguardRepository()
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        whenever(kosmos.accessibilityManagerWrapper.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+            .thenAnswer { it.arguments[0] }
 
         runBlocking { createUnderTest() }
     }
@@ -284,25 +285,22 @@
         isRevampedWppFeatureEnabled: Boolean = true,
         isOpenWppDirectlyEnabled: Boolean = false,
     ) {
+        // This needs to be re-created for each test outside of kosmos since the flag values are
+        // read during initialization to set up flows. Maybe there is a better way to handle that.
         underTest =
             KeyguardLongPressInteractor(
                 appContext = mContext,
                 scope = testScope.backgroundScope,
-                transitionInteractor =
-                    KeyguardTransitionInteractorFactory.create(
-                            scope = testScope.backgroundScope,
-                            repository = keyguardTransitionRepository,
-                        )
-                        .keyguardTransitionInteractor,
+                transitionInteractor = kosmos.keyguardTransitionInteractor,
                 repository = keyguardRepository,
                 logger = logger,
                 featureFlags =
-                    FakeFeatureFlags().apply {
+                    kosmos.fakeFeatureFlagsClassic.apply {
                         set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
                         set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
                     },
                 broadcastDispatcher = fakeBroadcastDispatcher,
-                accessibilityManager = accessibilityManager
+                accessibilityManager = kosmos.accessibilityManagerWrapper
             )
         setUpState()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index ce43d4e..9b302ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -19,23 +19,23 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
 import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -43,29 +43,22 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class LightRevealScrimInteractorTest : SysuiTestCase() {
-    private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
+    val kosmos =
+        testKosmos().apply {
+            this.fakeLightRevealScrimRepository = Mockito.spy(FakeLightRevealScrimRepository())
+        }
 
-    private val fakeLightRevealScrimRepository by lazy {
-        Mockito.spy(FakeLightRevealScrimRepository())
-    }
+    private val fakeLightRevealScrimRepository = kosmos.fakeLightRevealScrimRepository
 
-    private val testScope = TestScope()
+    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val testScope = kosmos.testScope
 
-    private val keyguardTransitionInteractor by lazy {
-        KeyguardTransitionInteractorFactory.create(
-                scope = testScope.backgroundScope,
-                repository = fakeKeyguardTransitionRepository,
-            )
-            .keyguardTransitionInteractor
-    }
-
-    private lateinit var underTest: LightRevealScrimInteractor
+    private val underTest = kosmos.lightRevealScrimInteractor
 
     private val reveal1 =
         object : LightRevealEffect {
@@ -77,19 +70,6 @@
             override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
         }
 
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        underTest =
-            LightRevealScrimInteractor(
-                keyguardTransitionInteractor,
-                fakeLightRevealScrimRepository,
-                testScope.backgroundScope,
-                mock(),
-                mock()
-            )
-    }
-
     @Test
     fun lightRevealEffect_doesNotChangeDuringKeyguardTransition() =
         runTest(UnconfinedTestDispatcher()) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6cc680b..2de013b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+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.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -57,7 +59,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardRootViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+        }
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val keyguardInteractor = kosmos.keyguardInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
index 2fe4ef78..f400cb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -25,9 +25,11 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
 import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
@@ -146,6 +148,7 @@
             kosmos.fakeKeyguardRepository.setIsDozing(false)
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                 AcquiredFingerprintAuthenticationStatus(
+                    AuthenticationReason.DeviceEntryAuthentication,
                     BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
                 )
             )
@@ -165,6 +168,7 @@
             kosmos.fakeKeyguardRepository.setIsDozing(true)
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                 AcquiredFingerprintAuthenticationStatus(
+                    AuthenticationReason.DeviceEntryAuthentication,
                     BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
                 )
             )
@@ -177,6 +181,7 @@
     private fun createViewModel() =
         SideFpsProgressBarViewModel(
             kosmos.applicationContext,
+            kosmos.biometricStatusInteractor,
             kosmos.deviceEntryFingerprintAuthInteractor,
             kosmos.sideFpsSensorInteractor,
             kosmos.dozeServiceHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index d9f24b3..9c0674d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.data.repository.fakePackageChangeRepository
 import com.android.systemui.common.data.repository.packageChangeRepository
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index b60f483..63fb67d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -50,7 +50,8 @@
 
     @Test
     fun mapsDisabledDataToInactiveState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
 
         val actualActivationState = tileState.activationState
 
@@ -59,7 +60,8 @@
 
     @Test
     fun mapsEnabledDataToActiveState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
 
         val actualActivationState = tileState.activationState
         assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
@@ -67,7 +69,8 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
@@ -77,7 +80,8 @@
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
@@ -86,11 +90,32 @@
     }
 
     @Test
-    fun supportsOnlyClickAction() {
+    fun mapsUnavailableDataToOffIconState() {
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+        val expectedIcon =
+            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+        val actualIcon = tileState.icon()
+        assertThat(actualIcon).isEqualTo(expectedIcon)
+    }
+
+    @Test
+    fun supportClickActionWhenAvailable() {
         val dontCare = true
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(dontCare))
 
         val supportedActions = tileState.supportedActions
         assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK)
     }
+
+    @Test
+    fun doesNotSupportClickActionWhenUnavailable() {
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+        val supportedActions = tileState.supportedActions
+        assertThat(supportedActions).isEmpty()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index a9e39354..c5a8c70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -70,8 +70,7 @@
     }
 
     @Test
-    fun dataMatchesController() = runTest {
-        controller.setFlashlight(false)
+    fun isEnabledDataMatchesControllerWhenAvailable() = runTest {
         val flowValues: List<FlashlightTileModel> by
             collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
 
@@ -81,8 +80,35 @@
         controller.setFlashlight(false)
         runCurrent()
 
-        assertThat(flowValues.size).isEqualTo(3)
-        assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder()
+        assertThat(flowValues.size).isEqualTo(4) // 2 from setup(), 2 from this test
+        assertThat(
+                flowValues.filterIsInstance<FlashlightTileModel.FlashlightAvailable>().map {
+                    it.isEnabled
+                }
+            )
+            .containsExactly(false, false, true, false)
+            .inOrder()
+    }
+
+    /**
+     * Simulates the scenario of changes in flashlight tile availability when camera is initially
+     * closed, then opened, and closed again.
+     */
+    @Test
+    fun availabilityDataMatchesControllerAvailability() = runTest {
+        val flowValues: List<FlashlightTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        controller.onFlashlightAvailabilityChanged(false)
+        runCurrent()
+        controller.onFlashlightAvailabilityChanged(true)
+        runCurrent()
+
+        assertThat(flowValues.size).isEqualTo(4) // 2 from setup + 2 from this test
+        assertThat(flowValues.map { it is FlashlightTileModel.FlashlightAvailable })
+            .containsExactly(true, true, false, true)
+            .inOrder()
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index 28d43b3..1f19c98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -29,7 +29,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
 @SmallTest
@@ -51,7 +53,7 @@
         assumeFalse(ActivityManager.isUserAMonkey())
         val stateBeforeClick = false
 
-        underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+        underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
 
         verify(controller).setFlashlight(!stateBeforeClick)
     }
@@ -61,8 +63,17 @@
         assumeFalse(ActivityManager.isUserAMonkey())
         val stateBeforeClick = true
 
-        underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+        underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
 
         verify(controller).setFlashlight(!stateBeforeClick)
     }
+
+    @Test
+    fun handleClickWhenUnavailable() = runTest {
+        assumeFalse(ActivityManager.isUserAMonkey())
+
+        underTest.handleInput(click(FlashlightTileModel.FlashlightTemporarilyUnavailable))
+
+        verify(controller, never()).setFlashlight(anyBoolean())
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
new file mode 100644
index 0000000..b7b3fdb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig
+
+    private val mapper by lazy {
+        FontScalingTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
+                .resources,
+            context.theme
+        )
+    }
+
+    @Test
+    fun activeStateMatchesEnabledModel() {
+        val inputModel = FontScalingTileModel
+
+        val outputState = mapper.map(fontScalingTileConfig, inputModel)
+
+        val expectedState = createFontScalingTileState()
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createFontScalingTileState(): QSTileState =
+        QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(
+                        R.drawable.ic_qs_font_scaling,
+                    )!!,
+                    null
+                )
+            },
+            context.getString(R.string.quick_settings_font_scaling_label),
+            QSTileState.ActivationState.ACTIVE,
+            null,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            context.getString(R.string.quick_settings_font_scaling_label),
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
new file mode 100644
index 0000000..39bc8a6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
@@ -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 com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileDataInteractorTest : SysuiTestCase() {
+    private val underTest: FontScalingTileDataInteractor = FontScalingTileDataInteractor()
+    private val testUser = UserHandle.of(1)
+
+    @Test
+    fun collectsExactlyOneValue() = runTest {
+        val flowValues by
+            collectValues(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+
+        Truth.assertThat(flowValues.size).isEqualTo(1)
+    }
+
+    @Test
+    fun lastValueIsNotEmpty() = runTest {
+        val flowValue by
+            collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+
+        Truth.assertThat(flowValue).isNotNull()
+    }
+
+    @Test
+    fun isAvailable() = runTest {
+        val availability by collectLastValue(underTest.availability(testUser))
+
+        Truth.assertThat(availability).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
new file mode 100644
index 0000000..2384915
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.provider.Settings
+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.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val keyguardStateController = FakeKeyguardStateController()
+
+    private lateinit var underTest: FontScalingTileUserActionInteractor
+
+    @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var dialog: SystemUIDialog
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+    @Before
+    fun setup() {
+        activityStarter = mock<ActivityStarter>()
+        dialogLaunchAnimator = mock<DialogLaunchAnimator>()
+        dialog = mock<SystemUIDialog>()
+        fontScalingDialogDelegate =
+            mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+        argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+
+        underTest =
+            FontScalingTileUserActionInteractor(
+                kosmos.testScope.coroutineContext,
+                qsTileIntentUserActionHandler,
+                { fontScalingDialogDelegate },
+                keyguardStateController,
+                dialogLaunchAnimator,
+                activityStarter
+            )
+    }
+
+    @Test
+    fun clickTile_screenUnlocked_showDialogAnimationFromView() =
+        kosmos.testScope.runTest {
+            keyguardStateController.isShowing = false
+            val testView = View(context)
+
+            underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+            verify(activityStarter)
+                .executeRunnableDismissingKeyguard(
+                    argumentCaptor.capture(),
+                    eq(null),
+                    eq(true),
+                    eq(true),
+                    eq(false)
+                )
+            argumentCaptor.value.run()
+            verify(dialogLaunchAnimator).showFromView(any(), eq(testView), nullable(), anyBoolean())
+        }
+
+    @Test
+    fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
+        kosmos.testScope.runTest {
+            keyguardStateController.isShowing = true
+            val testView = View(context)
+
+            underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+            verify(activityStarter)
+                .executeRunnableDismissingKeyguard(
+                    argumentCaptor.capture(),
+                    eq(null),
+                    eq(true),
+                    eq(true),
+                    eq(false)
+                )
+            argumentCaptor.value.run()
+            verify(dialogLaunchAnimator, never())
+                .showFromView(any(), eq(testView), nullable(), anyBoolean())
+            verify(dialog).show()
+        }
+
+    @Test
+    fun handleLongClick() =
+        kosmos.testScope.runTest {
+            underTest.handleInput(QSTileInputTestKtx.longClick(FontScalingTileModel))
+
+            Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+            val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+            val actualIntentAction = intentInput.intent.action
+            val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
+            Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 504ded3..9d3f0d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
@@ -262,6 +264,8 @@
                 simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
                 authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
                 windowController = mock(),
+                deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+                centralSurfaces = mock(),
             )
         startable.start()
 
@@ -518,6 +522,21 @@
             assertCurrentScene(SceneKey.Lockscreen)
         }
 
+    @Test
+    fun deviceProvisioningAndFactoryResetProtection() =
+        testScope.runTest {
+            val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
+            kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(false)
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+            assertThat(isVisible).isTrue()
+        }
+
     /**
      * Asserts that the current scene in the view-model matches what's expected.
      *
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 16cb623..34c5173 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
@@ -18,6 +18,7 @@
 
 package com.android.systemui.scene.domain.startable
 
+import android.app.StatusBarManager
 import android.os.PowerManager
 import android.platform.test.annotations.EnableFlags
 import android.view.Display
@@ -48,7 +49,10 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -63,6 +67,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
@@ -76,6 +81,7 @@
 class SceneContainerStartableTest : SysuiTestCase() {
 
     @Mock private lateinit var windowController: NotificationShadeWindowController
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -112,6 +118,8 @@
                 simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
                 authenticationInteractor = dagger.Lazy { authenticationInteractor },
                 windowController = windowController,
+                deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+                centralSurfaces = centralSurfaces,
             )
     }
 
@@ -162,6 +170,30 @@
         }
 
     @Test
+    fun hydrateVisibility_basedOnDeviceProvisioningAndFactoryResetProtection() =
+        testScope.runTest {
+            val isVisible by collectLastValue(sceneInteractor.isVisible)
+            prepareState(
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Lockscreen,
+                isDeviceProvisioned = false,
+                isFrpActive = true,
+            )
+
+            underTest.start()
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+            assertThat(isVisible).isTrue()
+
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+            assertThat(isVisible).isFalse()
+        }
+
+    @Test
     fun startsInLockscreenScene() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
@@ -736,6 +768,227 @@
             verify(windowController, times(2)).setNotificationShadeFocusable(false)
         }
 
+    @Test
+    fun hydrateInteractionState_whileLocked() =
+        testScope.runTest {
+            val transitionStateFlow =
+                prepareState(
+                    initialSceneKey = SceneKey.Lockscreen,
+                )
+            underTest.start()
+            runCurrent()
+            verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Bouncer,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            false,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            true,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Shade,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            false,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            true,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.QuickSettings,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+        }
+
+    @Test
+    fun hydrateInteractionState_whileUnlocked() =
+        testScope.runTest {
+            val transitionStateFlow =
+                prepareState(
+                    isDeviceUnlocked = true,
+                    initialSceneKey = SceneKey.Gone,
+                )
+            underTest.start()
+            verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Bouncer,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Shade,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.QuickSettings,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+        }
+
+    private fun TestScope.emulateSceneTransition(
+        transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+        toScene: SceneKey,
+        verifyBeforeTransition: (() -> Unit)? = null,
+        verifyDuringTransition: (() -> Unit)? = null,
+        verifyAfterTransition: (() -> Unit)? = null,
+    ) {
+        val fromScene = sceneInteractor.desiredScene.value.key
+        sceneInteractor.changeScene(SceneModel(toScene), "reason")
+        runCurrent()
+        verifyBeforeTransition?.invoke()
+
+        transitionStateFlow.value =
+            ObservableTransitionState.Transition(
+                fromScene = fromScene,
+                toScene = toScene,
+                progress = flowOf(0.5f),
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+        verifyDuringTransition?.invoke()
+
+        transitionStateFlow.value =
+            ObservableTransitionState.Idle(
+                scene = toScene,
+            )
+        runCurrent()
+        verifyAfterTransition?.invoke()
+    }
+
     private fun TestScope.prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
@@ -743,6 +996,8 @@
         authenticationMethod: AuthenticationMethodModel? = null,
         isLockscreenEnabled: Boolean = true,
         startsAwake: Boolean = true,
+        isDeviceProvisioned: Boolean = true,
+        isFrpActive: Boolean = false,
     ): MutableStateFlow<ObservableTransitionState> {
         if (authenticationMethod?.isSecure == true) {
             assert(isLockscreenEnabled) {
@@ -779,6 +1034,10 @@
         } else {
             powerInteractor.setAsleepForTest()
         }
+
+        kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned)
+        kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isFrpActive)
+
         runCurrent()
 
         return transitionStateFlow
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
new file mode 100644
index 0000000..d3c6598
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+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.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.recents.utilities.Utilities
+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.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeBackActionInteractorImplTest : SysuiTestCase() {
+    val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    val testScope = kosmos.testScope
+    val sceneInteractor = kosmos.sceneInteractor
+    val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+    val underTest = kosmos.shadeBackActionInteractor
+
+    @Before
+    fun ignoreSplitShade() {
+        Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext))
+    }
+
+    @Test
+    fun animateCollapseQs_notOnQs() =
+        testScope.runTest {
+            setScene(SceneKey.Shade)
+            underTest.animateCollapseQs(true)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+        }
+
+    @Test
+    fun animateCollapseQs_fullyCollapse_entered() =
+        testScope.runTest {
+            enterDevice()
+            setScene(SceneKey.QuickSettings)
+            underTest.animateCollapseQs(true)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun animateCollapseQs_fullyCollapse_locked() =
+        testScope.runTest {
+            deviceEntryRepository.setUnlocked(false)
+            setScene(SceneKey.QuickSettings)
+            underTest.animateCollapseQs(true)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun animateCollapseQs_notFullyCollapse() =
+        testScope.runTest {
+            setScene(SceneKey.QuickSettings)
+            underTest.animateCollapseQs(false)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+        }
+
+    private fun enterDevice() {
+        deviceEntryRepository.setUnlocked(true)
+        testScope.runCurrent()
+        setScene(SceneKey.Gone)
+    }
+
+    private fun setScene(key: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(key), "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        testScope.runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
new file mode 100644
index 0000000..693de55
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.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.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+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 NotificationsPlaceholderViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val underTest = kosmos.notificationsPlaceholderViewModel
+    @Test
+    fun onBoundsChanged_setsNotificationContainerBounds() {
+        underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
+        assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value)
+            .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+        assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value)
+            .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+    }
+    @Test
+    fun onContentTopChanged_setsContentTop() {
+        underTest.onContentTopChanged(padding = 5f)
+        assertThat(kosmos.notificationStackAppearanceInteractor.contentTop.value).isEqualTo(5f)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
index c669c6f..1f6ba29 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -33,6 +33,10 @@
         mOccluded = occluded;
     }
 
+    public void setShowing(boolean isShowing) {
+        mShowing = isShowing;
+    }
+
     @Override
     public boolean isShowing() {
         return mShowing;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 84b2c4b..53b262b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -21,7 +21,11 @@
 import android.graphics.Rect
 import android.view.Display
 import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.CameraProtectionInfo
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -38,30 +42,30 @@
 import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
 
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class StatusBarContentInsetsProviderTest : SysuiTestCase() {
 
-    @Mock private lateinit var dc: DisplayCutout
-    @Mock private lateinit var contextMock: Context
-    @Mock private lateinit var display: Display
-    private lateinit var configurationController: ConfigurationController
-
+    private val sysUICutout = mock<SysUICutoutInformation>()
+    private val dc = mock<DisplayCutout>()
+    private val contextMock = mock<Context>()
+    private val display = mock<Display>()
     private val configuration = Configuration()
 
+    private lateinit var configurationController: ConfigurationController
+
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(contextMock.display).thenReturn(display)
+        whenever(sysUICutout.cutout).thenReturn(dc)
+        whenever(contextMock.display).thenReturn(display)
 
         context.ensureTestableResources()
-        `when`(contextMock.resources).thenReturn(context.resources)
-        `when`(contextMock.resources.configuration).thenReturn(configuration)
-        `when`(contextMock.createConfigurationContext(any())).thenAnswer {
+        whenever(contextMock.resources).thenReturn(context.resources)
+        whenever(contextMock.resources.configuration).thenReturn(configuration)
+        whenever(contextMock.createConfigurationContext(any())).thenAnswer {
             context.createConfigurationContext(it.arguments[0] as Configuration)
         }
         configurationController = ConfigurationControllerImpl(contextMock)
@@ -117,7 +121,7 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout,
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
@@ -161,7 +165,7 @@
     }
 
     @Test
-    fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
+    fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_noCameraProtection() {
         // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
         val screenBounds = Rect(0, 0, 1080, 2160)
         val dcBounds = Rect(0, 0, 100, 100)
@@ -174,7 +178,7 @@
         val dotWidth = 10
         val statusBarContentHeight = 15
 
-        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+        whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
 
         // THEN rotations which share a short side should use the greater value between rounded
         // corner padding and the display cutout's size
@@ -187,7 +191,7 @@
         var bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout,
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
@@ -208,7 +212,7 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout,
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
@@ -231,7 +235,7 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout,
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
@@ -253,7 +257,335 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout,
+                screenBounds,
+                sbHeightLandscape,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_withCameraProtection() {
+        // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+        val screenBounds = Rect(0, 0, 1080, 2160)
+        val dcBounds = Rect(0, 0, 100, 100)
+        val protectionBounds = Rect(10, 10, 110, 110)
+        val minLeftPadding = 20
+        val minRightPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+        val isRtl = false
+        val dotWidth = 10
+        val statusBarContentHeight = 15
+
+        val protectionInfo = mock<CameraProtectionInfo> {
+            whenever(this.cutoutBounds).thenReturn(protectionBounds)
+        }
+        whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+        whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+        // THEN rotations which share a short side should use the greater value between rounded
+        // corner padding, the display cutout's size, and the camera protections' size.
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(protectionBounds.right,
+                0,
+                screenBounds.right - minRightPadding,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(protectionBounds.bottom,
+                0,
+                screenBounds.height() - minRightPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightLandscape,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // THEN the side that does NOT share a short side with the display cutout ignores the
+        // display cutout bounds
+        targetRotation = ROTATION_UPSIDE_DOWN
+        expectedBounds = Rect(minLeftPadding,
+                0,
+                screenBounds.width() - minRightPadding,
+                sbHeightPortrait)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // Phone in portrait, seascape (rot_270) bounds
+        targetRotation = ROTATION_SEASCAPE
+        expectedBounds = Rect(minLeftPadding,
+                0,
+                screenBounds.height() - protectionBounds.bottom - dotWidth,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightLandscape,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_noCameraProtection() {
+        // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+        val screenBounds = Rect(0, 0, 1000, 2000)
+        val dcBounds = Rect(900, 0, 1000, 100)
+        val minLeftPadding = 20
+        val minRightPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+        val isRtl = false
+        val dotWidth = 10
+        val statusBarContentHeight = 15
+
+        whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+        // THEN rotations which share a short side should use the greater value between rounded
+        // corner padding and the display cutout's size
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(minLeftPadding,
+                0,
+                dcBounds.left - dotWidth,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(dcBounds.height(),
+                0,
+                screenBounds.height() - minRightPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightLandscape,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // THEN the side that does NOT share a short side with the display cutout ignores the
+        // display cutout bounds
+        targetRotation = ROTATION_UPSIDE_DOWN
+        expectedBounds = Rect(minLeftPadding,
+                0,
+                screenBounds.width() - minRightPadding,
+                sbHeightPortrait)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // Phone in portrait, seascape (rot_270) bounds
+        targetRotation = ROTATION_SEASCAPE
+        expectedBounds = Rect(minLeftPadding,
+                0,
+                screenBounds.height() - dcBounds.height() - dotWidth,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightLandscape,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_withCameraProtection() {
+        // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+        val screenBounds = Rect(0, 0, 1000, 2000)
+        val dcBounds = Rect(900, 0, 1000, 100)
+        val protectionBounds = Rect(890, 10, 990, 110)
+        val minLeftPadding = 20
+        val minRightPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+        val isRtl = false
+        val dotWidth = 10
+        val statusBarContentHeight = 15
+
+        val protectionInfo = mock<CameraProtectionInfo> {
+            whenever(this.cutoutBounds).thenReturn(protectionBounds)
+        }
+        whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+        whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+        // THEN rotations which share a short side should use the greater value between rounded
+        // corner padding, the display cutout's size, and the camera protections' size.
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(minLeftPadding,
+                0,
+                protectionBounds.left - dotWidth,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(protectionBounds.bottom,
+                0,
+                screenBounds.height() - minRightPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightLandscape,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // THEN the side that does NOT share a short side with the display cutout ignores the
+        // display cutout bounds
+        targetRotation = ROTATION_UPSIDE_DOWN
+        expectedBounds = Rect(minLeftPadding,
+                0,
+                screenBounds.width() - minRightPadding,
+                sbHeightPortrait)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
+                screenBounds,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding,
+                isRtl,
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // Phone in portrait, seascape (rot_270) bounds
+        targetRotation = ROTATION_SEASCAPE
+        expectedBounds = Rect(minLeftPadding,
+                0,
+                screenBounds.height() - protectionBounds.bottom - dotWidth,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                sysUICutout,
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
@@ -273,7 +605,7 @@
         val bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation = ROTATION_NONE,
                 targetRotation = ROTATION_NONE,
-                displayCutout = dc,
+                sysUICutout = sysUICutout,
                 maxBounds = Rect(0, 0, 1080, 2160),
                 statusBarHeight = 100,
                 minLeft = 0,
@@ -293,7 +625,7 @@
         val bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation = ROTATION_NONE,
                 targetRotation = ROTATION_NONE,
-                displayCutout = dc,
+                sysUICutout = sysUICutout,
                 maxBounds = Rect(0, 0, 1080, 2160),
                 statusBarHeight = 100,
                 minLeft = 0,
@@ -321,6 +653,7 @@
         val screenBounds = Rect(0, 0, 1080, 2160)
         // cutout centered at the top
         val dcBounds = Rect(490, 0, 590, 100)
+        val protectionBounds = Rect(480, 10, 600, 90)
         val minLeftPadding = 20
         val minRightPadding = 20
         val sbHeightPortrait = 100
@@ -330,7 +663,11 @@
         val dotWidth = 10
         val statusBarContentHeight = 15
 
-        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+        val protectionInfo = mock<CameraProtectionInfo> {
+            whenever(this.cutoutBounds).thenReturn(protectionBounds)
+        }
+        whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+        whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
 
         // THEN only the landscape/seascape rotations should avoid the cutout area because of the
         // potential letterboxing
@@ -343,7 +680,7 @@
         var bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout = sysUICutout,
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
@@ -364,7 +701,7 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout = sysUICutout,
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
@@ -385,7 +722,7 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout = sysUICutout,
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
@@ -406,7 +743,7 @@
         bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout = sysUICutout,
                 screenBounds,
                 sbHeightLandscape,
                 minLeftPadding,
@@ -528,7 +865,7 @@
         val dotWidth = 10
         val statusBarContentHeight = 15
 
-        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+        whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
 
         // THEN left should be set to the display cutout width, and right should use the minRight
         val targetRotation = ROTATION_NONE
@@ -540,7 +877,7 @@
         val bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                dc,
+                sysUICutout,
                 screenBounds,
                 sbHeightPortrait,
                 minLeftPadding,
@@ -557,7 +894,7 @@
     fun testDisplayChanged_returnsUpdatedInsets() {
         // GIVEN: get insets on the first display and switch to the second display
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-            mock<DumpManager>(), mock<CommandRegistry>())
+            mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
 
         configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -576,7 +913,7 @@
         // GIVEN: get insets on the first display, switch to the second display,
         // get insets and switch back
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-            mock<DumpManager>(), mock<CommandRegistry>())
+            mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
 
         configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsetsFirstCall = provider
@@ -602,7 +939,7 @@
         configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
         configurationController.onConfigurationChanged(configuration)
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-                mock<DumpManager>(), mock<CommandRegistry>())
+                mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
         val listener = object : StatusBarContentInsetsChangedListener {
             var triggered = false
 
@@ -624,7 +961,7 @@
     fun onDensityOrFontScaleChanged_listenerNotified() {
         configuration.densityDpi = 12
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-                mock<DumpManager>(), mock<CommandRegistry>())
+                mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
         val listener = object : StatusBarContentInsetsChangedListener {
             var triggered = false
 
@@ -645,7 +982,7 @@
     @Test
     fun onThemeChanged_listenerNotified() {
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
-                mock<DumpManager>(), mock<CommandRegistry>())
+                mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
         val listener = object : StatusBarContentInsetsChangedListener {
             var triggered = false
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 9063a02..682a68f 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.bp b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
index 66951b5..f6fa6a5 100644
--- a/packages/SystemUI/plugin/ExamplePlugin/Android.bp
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
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 4436be7..fd7a7f3 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
@@ -116,6 +116,8 @@
 
     /** Custom constraints to apply to Lockscreen ConstraintLayout. */
     fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+
+    fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
 }
 
 /** A ClockFaceLayout that applies the default lockscreen layout to a single view */
@@ -131,6 +133,10 @@
         }
         return constraints
     }
+
+    override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
+        return constraints
+    }
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index b7e1545..521c019 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
new file mode 100644
index 0000000..ad22894
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <shape android:shape="oval">
+            <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
new file mode 100644
index 0000000..8c2b036
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="16dp" />
+            <stroke android:width="3dp"
+                android:color="@color/bouncer_password_focus_color" />
+            <padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp"/>
+        </shape>
+    </item>
+    <item>
+        <inset android:insetLeft="-4dp"
+            android:insetRight="-4dp"
+            android:insetTop="-4dp">
+            <shape android:shape="rectangle">
+                <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+            </shape>
+        </inset>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
similarity index 100%
rename from packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
rename to packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 2fc1d2e..909d4fc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -54,7 +54,7 @@
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:contentDescription="@string/keyguard_accessibility_password"
-             android:gravity="center_horizontal"
+             android:gravity="center"
              android:singleLine="true"
              android:textStyle="normal"
              android:inputType="textPassword"
@@ -68,14 +68,14 @@
          <ImageView android:id="@+id/switch_ime_button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
-             android:layout_marginBottom="12dp"
              android:src="@drawable/ic_lockscreen_ime"
              android:contentDescription="@string/accessibility_ime_switch_button"
              android:clickable="true"
-             android:padding="8dip"
+             android:layout_marginRight="8dp"
+             android:padding="12dip"
              android:tint="?android:attr/textColorPrimary"
              android:layout_gravity="end|center_vertical"
-             android:background="?android:attr/selectableItemBackground"
+             android:background="@drawable/bouncer_input_method_background"
              android:visibility="gone"
              />
        </FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index ddad1e3..e853f02 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -28,8 +28,14 @@
     <!-- Width for the keyguard pin input field -->
     <dimen name="keyguard_pin_field_width">292dp</dimen>
 
-    <!-- Width for the keyguard pin input field -->
-    <dimen name="keyguard_pin_field_height">48dp</dimen>
+    <!-- height for the keyguard pin input field -->
+    <dimen name="keyguard_pin_field_height">56dp</dimen>
+
+    <!-- height for the keyguard password input field -->
+    <dimen name="keyguard_password_field_height">56dp</dimen>
+
+    <!-- width for the keyguard password input field -->
+    <dimen name="keyguard_password_field_width">276dp</dimen>
 
     <!-- Height of the sliding KeyguardSecurityContainer
          (includes 2x keyguard_security_view_top_margin) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4789a22..c43e394 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,7 @@
     </style>
     <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
+        <item name="android:background">@drawable/bouncer_pin_view_focused_background</item>
         <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml
new file mode 100644
index 0000000..6028769
--- /dev/null
+++ b/packages/SystemUI/res/color/brightness_slider_track.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_500" android:lStar="40" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 95c7778c..cae9d6b 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -24,7 +24,7 @@
             <shape>
                 <size android:height="@dimen/rounded_slider_track_width" />
                 <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
-                <solid android:color="?attr/shadeInactive" />
+                <solid android:color="@color/brightness_slider_track" />
             </shape>
         </inset>
     </item>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
new file mode 100644
index 0000000..a877853
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
+android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/logo"
+        android:layout_width="@dimen/biometric_auth_icon_size"
+        android:layout_height="@dimen/biometric_auth_icon_size"
+        android:layout_gravity="center"
+        android:scaleType="fitXY"
+        android:visibility="gone" />
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <View
+        android:id="@+id/panel"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:attr/colorBackgroundFloating"
+        android:clickable="true"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp"
+        android:visibility="visible"
+        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/title" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.8"
+        tools:srcCompat="@tools:sample/avatars" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon_overlay"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_gravity="center"
+        android:contentDescription="@null"
+        android:scaleType="fitXY"
+        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+        app:layout_constraintTop_toTopOf="@+id/biometric_icon"
+        app:layout_constraintVertical_bias="0.0" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:singleLine="true"
+        android:marqueeRepeatLimit="1"
+        android:ellipsize="marquee"
+        style="@style/TextAppearance.AuthCredential.Title"
+        app:layout_constraintBottom_toTopOf="@+id/subtitle"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:singleLine="true"
+        android:marqueeRepeatLimit="1"
+        android:ellipsize="marquee"
+        style="@style/TextAppearance.AuthCredential.Subtitle"
+        app:layout_constraintBottom_toTopOf="@+id/description"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <Space
+        android:id="@+id/space_above_content"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/biometric_prompt_space_above_content"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@+id/subtitle"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+    <ScrollView
+        android:id="@+id/customized_view_container"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:fillViewport="true"
+        android:fadeScrollbars="false"
+        android:gravity="center_vertical"
+        android:orientation="vertical"
+        android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+        android:scrollbars="vertical"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@+id/space_above_content"
+        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="24dp"
+        android:scrollbars="vertical"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        style="@style/TextAppearance.AuthCredential.Description"
+        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:gravity="center_horizontal"
+        android:textColor="@color/biometric_dialog_gray"
+        android:textSize="12sp"
+        android:accessibilityLiveRegion="polite"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:scrollHorizontally="true"
+        android:fadingEdge="horizontal"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="@+id/panel"
+        app:layout_constraintTop_toBottomOf="@+id/biometric_icon" />
+
+    <!-- Negative Button, reserved for app -->
+    <Button
+        android:id="@+id/button_negative"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <!-- Cancel Button, replaces negative button when biometric is accepted -->
+    <Button
+        android:id="@+id/button_cancel"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:text="@string/cancel"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <!-- "Use Credential" Button, replaces if device credential is allowed -->
+    <Button
+        android:id="@+id/button_use_credential"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <!-- Positive Button -->
+    <Button
+        android:id="@+id/button_confirm"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_confirm"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        tools:visibility="invisible" />
+
+    <!-- Try Again Button -->
+    <Button
+        android:id="@+id/button_try_again"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_try_again"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintEnd_toEndOf="@+id/panel" />
+
+    <!-- Guidelines for setting panel border -->
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/leftGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/rightGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/bottomGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 6e541a7..ce09385 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -49,7 +49,7 @@
             android:layout_centerVertical="true"
             android:paddingTop="1dp"
             android:importantForAccessibility="yes"
-            android:tint="#9E9E9E" />
+            android:tint="?android:attr/textColorPrimary"/>
 
         <TextView
             android:id="@+id/undo"
diff --git a/packages/SystemUI/res/values-xxhdpi/dimens.xml b/packages/SystemUI/res/values-xxhdpi/dimens.xml
index 26c8437..9bff422 100644
--- a/packages/SystemUI/res/values-xxhdpi/dimens.xml
+++ b/packages/SystemUI/res/values-xxhdpi/dimens.xml
@@ -22,4 +22,8 @@
          fraction of a pixel.-->
     <fraction name="battery_subpixel_smoothing_left">33%</fraction>
     <fraction name="battery_subpixel_smoothing_right">33%</fraction>
+
+    <!-- Biometrics fingerprint icon size for full resolution.-->
+    <dimen name="biometric_dialog_fingerprint_icon_width">120dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_height">120dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7a83070..65c69f7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -542,6 +542,9 @@
     <string translatable="false" name="config_protectedCameraId"></string>
     <!-- Physical ID for the camera of outer display that needs extra protection -->
     <string translatable="false" name="config_protectedPhysicalCameraId"></string>
+    <!-- Unique ID of the outer display that contains the camera that needs protection. -->
+    <string translatable="false" name="config_protectedScreenUniqueId"></string>
+
 
     <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
     <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
@@ -550,6 +553,8 @@
     <string translatable="false" name="config_protectedInnerCameraId"></string>
     <!-- Physical ID for the camera of inner display that needs extra protection -->
     <string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
+    <!-- Unique ID of the inner display that contains the camera that needs protection. -->
+    <string translatable="false" name="config_protectedInnerScreenUniqueId"></string>
 
     <!-- Comma-separated list of packages to exclude from camera protection e.g.
     "com.android.systemui,com.android.xyz" -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 33bdca3..cc31754 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -174,7 +174,7 @@
     <dimen name="status_bar_clock_size">14sp</dimen>
 
     <!-- The starting padding for the clock in the status bar. -->
-    <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+    <dimen name="status_bar_clock_starting_padding">4dp</dimen>
 
     <!-- The end padding for the clock in the status bar. -->
     <dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -318,9 +318,6 @@
     notification panel collapses -->
     <dimen name="shelf_appear_translation">42dp</dimen>
 
-    <!-- Vertical translation of pulsing notification animations -->
-    <dimen name="pulsing_notification_appear_translation">10dp</dimen>
-
     <!-- The amount the content shifts upwards when transforming into the shelf -->
     <dimen name="shelf_transform_content_shift">32dp</dimen>
 
@@ -398,7 +395,7 @@
     <dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
 
     <!-- the padding on the start of the statusbar -->
-    <dimen name="status_bar_padding_start">8dp</dimen>
+    <dimen name="status_bar_padding_start">4dp</dimen>
 
     <!-- the padding on the end of the statusbar -->
     <dimen name="status_bar_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8971859..1b71256 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -305,6 +305,27 @@
     <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
     <string name="screenrecord_start_error">Error starting screen recording</string>
 
+    <!-- Notification title displayed for issue recording [CHAR LIMIT=50]-->
+    <string name="issuerecord_title">Issue Recorder</string>
+    <!-- Processing issue recoding data in the background [CHAR LIMIT=30]-->
+    <string name="issuerecord_background_processing_label">Processing issue recording</string>
+    <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]-->
+    <string name="issuerecord_channel_description">Ongoing notification for an issue collection session</string>
+
+    <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]-->
+    <string name="issuerecord_ongoing_screen_only">Recording issue</string>
+    <!-- Label for notification action to share issue recording [CHAR LIMIT=35] -->
+    <string name="issuerecord_share_label">Share</string>
+    <!-- A toast message shown after successfully canceling a issue recording [CHAR LIMIT=NONE] -->
+    <!-- Notification text shown after saving a issue recording [CHAR LIMIT=100] -->
+    <string name="issuerecord_save_title">Issue recording saved</string>
+    <!-- Subtext for a notification shown after saving a issue recording to prompt the user to view it [CHAR_LIMIT=100] -->
+    <string name="issuerecord_save_text">Tap to view</string>
+    <!-- A toast message shown when there is an error saving a issue recording [CHAR LIMIT=NONE] -->
+    <string name="issuerecord_save_error">Error saving issue recording</string>
+    <!-- A toast message shown when the issue recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
+    <string name="issuerecord_start_error">Error starting issue recording</string>
+
     <!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
     <string name="immersive_cling_title">Viewing full screen</string>
     <!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
@@ -3316,6 +3337,6 @@
     <string name="keyboard_backlight_value">Level %1$d of %2$d</string>
     <!-- Label for home control panel [CHAR LIMIT=30] -->
     <string name="home_controls_dream_label">Home Controls</string>
-    <!-- Description for home control panel [CHAR LIMIT=50] -->
+    <!-- Description for home control panel [CHAR LIMIT=67] -->
     <string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
 </resources>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 4e04af6..6e611fe 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
@@ -54,7 +55,7 @@
         "SystemUIUnfoldLib",
         "SystemUISharedLib-Keyguard",
         "WindowManager-Shell-shared",
-        "tracinglib",
+        "tracinglib-platform",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
         "androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index dfeb1f3..d821f19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,7 +33,7 @@
 public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
     protected View mEcaView;
 
-    // To avoid accidental lockout due to events while the device in in the pocket, ignore
+    // To avoid accidental lockout due to events while the device in the pocket, ignore
     // any passwords with length less than or equal to this length.
     protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
     private KeyDownListener mKeyDownListener;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index efd8f7f..1a10c7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,6 +30,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.Flags;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.ui.BouncerMessageView;
@@ -212,6 +213,7 @@
         private final SelectedUserInteractor mSelectedUserInteractor;
         private final UiEventLogger mUiEventLogger;
         private final KeyboardRepository mKeyboardRepository;
+        private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -226,7 +228,8 @@
                 KeyguardViewController keyguardViewController,
                 FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
                 UiEventLogger uiEventLogger,
-                KeyboardRepository keyboardRepository) {
+                KeyboardRepository keyboardRepository,
+                KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -244,6 +247,7 @@
             mSelectedUserInteractor = selectedUserInteractor;
             mUiEventLogger = uiEventLogger;
             mKeyboardRepository = keyboardRepository;
+            mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -265,7 +269,8 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
                         mFalsingCollector, mKeyguardViewController,
-                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
+                        mKeyguardKeyboardInteractor);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 3d8aaaf..7473e0c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -17,8 +17,10 @@
 package com.android.keyguard;
 
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.text.Editable;
 import android.text.InputType;
@@ -27,6 +29,7 @@
 import android.text.method.TextKeyListener;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
@@ -39,6 +42,8 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
+import com.android.systemui.Flags;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -52,6 +57,7 @@
 public class KeyguardPasswordViewController
         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
 
+    private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final DevicePostureController mPostureController;
     private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -60,6 +66,8 @@
     private final DelayableExecutor mMainExecutor;
     private final KeyguardViewController mKeyguardViewController;
     private final boolean mShowImeAtScreenOn;
+    private Drawable mDefaultPasswordFieldBackground;
+    private Drawable mFocusedPasswordFieldBackground;
     private EditText mPasswordEntry;
     private ImageView mSwitchImeButton;
     private boolean mPaused;
@@ -121,7 +129,8 @@
             KeyguardViewController keyguardViewController,
             DevicePostureController postureController,
             FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor);
@@ -130,11 +139,15 @@
         mPostureController = postureController;
         mMainExecutor = mainExecutor;
         mKeyguardViewController = keyguardViewController;
+        mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
             view.setIsLockScreenLandscapeEnabled();
         }
         mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+        mDefaultPasswordFieldBackground = mPasswordEntry.getBackground();
+        mFocusedPasswordFieldBackground = getResources().getDrawable(
+                R.drawable.bouncer_password_view_background);
         mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
     }
 
@@ -175,6 +188,27 @@
 
         // If there's more than one IME, enable the IME switcher button
         updateSwitchImeButton();
+
+        if (Flags.pinInputFieldStyledFocusState()) {
+            collectFlow(mPasswordEntry,
+                    mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
+                    this::setPasswordFieldFocusBackground);
+
+            ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+            layoutParams.height = (int) getResources()
+                    .getDimension(R.dimen.keyguard_password_field_height);
+            layoutParams.width = (int) getResources()
+                    .getDimension(R.dimen.keyguard_password_field_width);
+        }
+
+    }
+
+    private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) {
+        if (isAnyKeyboardConnected) {
+            mPasswordEntry.setBackground(mFocusedPasswordFieldBackground);
+        } else {
+            mPasswordEntry.setBackground(mDefaultPasswordFieldBackground);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index bc12aee..ce03072 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -132,7 +132,7 @@
     boolean shouldSubtleWindowAnimationsForUnlock();
 
     /**
-     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+     * Starts the animation before we dismiss Keyguard, i.e. a disappearing animation on the
      * security view of the bouncer.
      *
      * @param finishRunnable the runnable to be run after the animation finished, or {@code null} if
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ef65144..9ebae90 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -109,8 +109,12 @@
                 animProps.setDelay(0).setDuration(160);
                 log("goingToFullShade && !keyguardFadingAway");
             }
-            PropertyAnimator.setProperty(
-                    mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+            if (KeyguardShadeMigrationNssl.isEnabled()) {
+                log("Using LockscreenToGoneTransition 1");
+            } else {
+                PropertyAnimator.setProperty(
+                        mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+            }
         } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
             mView.setVisibility(View.VISIBLE);
             mKeyguardViewVisibilityAnimating = true;
@@ -179,9 +183,13 @@
                 mView.setVisibility(View.VISIBLE);
             }
         } else {
-            log("Direct set Visibility to GONE");
-            mView.setVisibility(View.GONE);
-            mView.setAlpha(1f);
+            if (KeyguardShadeMigrationNssl.isEnabled()) {
+                log("Using LockscreenToGoneTransition 2");
+            } else {
+                log("Direct set Visibility to GONE");
+                mView.setVisibility(View.GONE);
+                mView.setAlpha(1f);
+            }
         }
 
         mLastOccludedState = isOccluded;
diff --git a/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
new file mode 100644
index 0000000..c39d3e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardKeyboardInteractor @Inject constructor(keyboardRepository: KeyboardRepository) {
+    val isAnyKeyboardConnected: Flow<Boolean> = keyboardRepository.isAnyKeyboardConnected
+}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 5f5cca8..e8499d3 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,15 +17,11 @@
 package com.android.systemui
 
 import android.content.Context
-import android.content.res.Resources
 import android.graphics.Path
 import android.graphics.Rect
-import android.graphics.RectF
 import android.hardware.camera2.CameraManager
-import android.util.PathParser
 import com.android.systemui.res.R
 import java.util.concurrent.Executor
-import kotlin.math.roundToInt
 
 /**
  * Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
@@ -163,89 +159,20 @@
     }
 
     companion object Factory {
-        fun build(context: Context, executor: Executor): CameraAvailabilityListener {
+        fun build(
+            context: Context,
+            executor: Executor,
+            cameraProtectionLoader: CameraProtectionLoader
+        ): CameraAvailabilityListener {
             val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
             val res = context.resources
-            val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
+            val cameraProtectionInfoList = cameraProtectionLoader.loadCameraProtectionInfoList()
             val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
 
             return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor)
         }
-
-        private fun pathFromString(pathString: String): Path {
-            val spec = pathString.trim()
-            val p: Path
-            try {
-                p = PathParser.createPathFromPathData(spec)
-            } catch (e: Throwable) {
-                throw IllegalArgumentException("Invalid protection path", e)
-            }
-
-            return p
-        }
-
-        private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
-            val list = mutableListOf<CameraProtectionInfo>()
-            val front =
-                loadCameraProtectionInfo(
-                    res,
-                    R.string.config_protectedCameraId,
-                    R.string.config_protectedPhysicalCameraId,
-                    R.string.config_frontBuiltInDisplayCutoutProtection
-                )
-            if (front != null) {
-                list.add(front)
-            }
-            val inner =
-                loadCameraProtectionInfo(
-                    res,
-                    R.string.config_protectedInnerCameraId,
-                    R.string.config_protectedInnerPhysicalCameraId,
-                    R.string.config_innerBuiltInDisplayCutoutProtection
-                )
-            if (inner != null) {
-                list.add(inner)
-            }
-            return list
-        }
-
-        private fun loadCameraProtectionInfo(
-            res: Resources,
-            cameraIdRes: Int,
-            physicalCameraIdRes: Int,
-            pathRes: Int
-        ): CameraProtectionInfo? {
-            val logicalCameraId = res.getString(cameraIdRes)
-            if (logicalCameraId.isNullOrEmpty()) {
-                return null
-            }
-            val physicalCameraId = res.getString(physicalCameraIdRes)
-            val protectionPath = pathFromString(res.getString(pathRes))
-            val computed = RectF()
-            protectionPath.computeBounds(computed)
-            val protectionBounds =
-                Rect(
-                    computed.left.roundToInt(),
-                    computed.top.roundToInt(),
-                    computed.right.roundToInt(),
-                    computed.bottom.roundToInt()
-                )
-            return CameraProtectionInfo(
-                logicalCameraId,
-                physicalCameraId,
-                protectionPath,
-                protectionBounds
-            )
-        }
     }
 
-    data class CameraProtectionInfo(
-        val logicalCameraId: String,
-        val physicalCameraId: String?,
-        val cutoutProtectionPath: Path,
-        val cutoutBounds: Rect,
-    )
-
     private data class OpenCameraInfo(
         val logicalCameraId: String,
         val packageId: String,
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
new file mode 100644
index 0000000..6314bd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+
+data class CameraProtectionInfo(
+    val logicalCameraId: String,
+    val physicalCameraId: String?,
+    val cutoutProtectionPath: Path,
+    val cutoutBounds: Rect,
+    val displayUniqueId: String?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
new file mode 100644
index 0000000..6cee28b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.Context
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.PathParser
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+
+interface CameraProtectionLoader {
+    fun loadCameraProtectionInfoList(): List<CameraProtectionInfo>
+}
+
+class CameraProtectionLoaderImpl @Inject constructor(private val context: Context) :
+    CameraProtectionLoader {
+
+    override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+        val list = mutableListOf<CameraProtectionInfo>()
+        val front =
+            loadCameraProtectionInfo(
+                R.string.config_protectedCameraId,
+                R.string.config_protectedPhysicalCameraId,
+                R.string.config_frontBuiltInDisplayCutoutProtection,
+                R.string.config_protectedScreenUniqueId,
+            )
+        if (front != null) {
+            list.add(front)
+        }
+        val inner =
+            loadCameraProtectionInfo(
+                R.string.config_protectedInnerCameraId,
+                R.string.config_protectedInnerPhysicalCameraId,
+                R.string.config_innerBuiltInDisplayCutoutProtection,
+                R.string.config_protectedInnerScreenUniqueId,
+            )
+        if (inner != null) {
+            list.add(inner)
+        }
+        return list
+    }
+
+    private fun loadCameraProtectionInfo(
+        cameraIdRes: Int,
+        physicalCameraIdRes: Int,
+        pathRes: Int,
+        displayUniqueIdRes: Int,
+    ): CameraProtectionInfo? {
+        val logicalCameraId = context.getString(cameraIdRes)
+        if (logicalCameraId.isNullOrEmpty()) {
+            return null
+        }
+        val physicalCameraId = context.getString(physicalCameraIdRes)
+        val protectionPath = pathFromString(context.getString(pathRes))
+        val computed = RectF()
+        protectionPath.computeBounds(computed)
+        val protectionBounds =
+            Rect(
+                computed.left.roundToInt(),
+                computed.top.roundToInt(),
+                computed.right.roundToInt(),
+                computed.bottom.roundToInt()
+            )
+        val displayUniqueId = context.getString(displayUniqueIdRes)
+        return CameraProtectionInfo(
+            logicalCameraId,
+            physicalCameraId,
+            protectionPath,
+            protectionBounds,
+            displayUniqueId
+        )
+    }
+
+    private fun pathFromString(pathString: String): Path {
+        return try {
+            PathParser.createPathFromPathData(pathString.trim())
+        } catch (e: Throwable) {
+            throw IllegalArgumentException("Invalid protection path", e)
+        }
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
similarity index 61%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
index a368de2..58680a8 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CameraProtectionModule {
+
+    @Binds fun cameraProtectionLoaderImpl(impl: CameraProtectionLoaderImpl): CameraProtectionLoader
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index d6d5c26..3e03fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -146,6 +146,7 @@
     private final ThreadFactory mThreadFactory;
     private final DecorProviderFactory mDotFactory;
     private final FaceScanningProviderFactory mFaceScanningFactory;
+    private final CameraProtectionLoader mCameraProtectionLoader;
     public final int mFaceScanningViewId;
 
     @VisibleForTesting
@@ -333,7 +334,8 @@
             FaceScanningProviderFactory faceScanningFactory,
             ScreenDecorationsLogger logger,
             FacePropertyRepository facePropertyRepository,
-            JavaAdapter javaAdapter) {
+            JavaAdapter javaAdapter,
+            CameraProtectionLoader cameraProtectionLoader) {
         mContext = context;
         mSecureSettings = secureSettings;
         mCommandRegistry = commandRegistry;
@@ -343,6 +345,7 @@
         mThreadFactory = threadFactory;
         mDotFactory = dotFactory;
         mFaceScanningFactory = faceScanningFactory;
+        mCameraProtectionLoader = cameraProtectionLoader;
         mFaceScanningViewId = com.android.systemui.res.R.id.face_scanning_anim;
         mLogger = logger;
         mFacePropertyRepository = facePropertyRepository;
@@ -981,7 +984,9 @@
         Resources res = mContext.getResources();
         boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
         if (enabled) {
-            mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mExecutor);
+            mCameraListener =
+                    CameraAvailabilityListener.Factory.build(
+                            mContext, mExecutor, mCameraProtectionLoader);
             mCameraListener.addTransitionCallback(mCameraTransitionCallback);
             mCameraListener.startListening();
         }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
similarity index 64%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
index a368de2..fc0b97e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
+import android.view.DisplayCutout
+
+data class SysUICutoutInformation(
+    val cutout: DisplayCutout,
+    val cameraProtection: CameraProtectionInfo?
 )
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
new file mode 100644
index 0000000..aad9341
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.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
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class SysUICutoutProvider
+@Inject
+constructor(
+    private val context: Context,
+    private val cameraProtectionLoader: CameraProtectionLoader,
+) {
+
+    private val cameraProtectionList by lazy {
+        cameraProtectionLoader.loadCameraProtectionInfoList()
+    }
+
+    fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+        val display = context.display
+        val displayCutout: DisplayCutout = display.cutout ?: return null
+        val displayUniqueId: String? = display.uniqueId
+        if (displayUniqueId.isNullOrEmpty()) {
+            return SysUICutoutInformation(displayCutout, cameraProtection = null)
+        }
+        val cameraProtection: CameraProtectionInfo? =
+            cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
+        return SysUICutoutInformation(displayCutout, cameraProtection)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index c7e5b64..c23a051 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -61,7 +61,8 @@
                 .observerFlow(userHandle.identifier, SETTING_NAME)
                 .onStart { emit(Unit) }
                 .map {
-                    secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+                    secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+                        ENABLED
                 }
                 .distinctUntilChanged()
                 .flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index 419eada..8525797 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -61,7 +61,8 @@
                 .observerFlow(userHandle.identifier, SETTING_NAME)
                 .onStart { emit(Unit) }
                 .map {
-                    secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+                    secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+                        ENABLED
                 }
                 .distinctUntilChanged()
                 .flowOn(bgCoroutineContext)
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 4a06ae9..0538e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -21,6 +21,7 @@
 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 android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED;
@@ -46,7 +47,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Prefs;
@@ -182,7 +182,7 @@
     }
 
     void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) {
-        callback.onReady(getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
+        callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON));
     }
 
     void loadMenuSizeType(OnInfoReady<Integer> callback) {
@@ -223,7 +223,7 @@
 
     private void onTargetFeaturesChanged() {
         mSettingsContentsCallback.onTargetFeaturesChanged(
-                getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
+                getTargets(mContext, ACCESSIBILITY_BUTTON));
     }
 
     private Position getStartPosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index f2e9531..a883c00 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.view.WindowInsets.Type.ime;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static androidx.core.view.WindowInsetsCompat.Type;
 
@@ -63,7 +64,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto;
@@ -154,7 +154,7 @@
 
             final List<ComponentName> hardwareKeyShortcutComponents =
                     mAccessibilityManager.getAccessibilityShortcutTargets(
-                                    ShortcutConstants.UserShortcutType.HARDWARE)
+                                    ACCESSIBILITY_SHORTCUT_KEY)
                             .stream()
                             .map(ComponentName::unflattenFromString)
                             .toList();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 135ab35..4047623 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
 import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -93,6 +97,7 @@
     companion object {
         const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
         const val COLOR_INVERSION_TILE_SPEC = "inversion"
+        const val FONT_SCALING_TILE_SPEC = "font_scaling"
 
         @Provides
         @IntoMap
@@ -155,5 +160,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(FONT_SCALING_TILE_SPEC)
+        fun provideFontScalingTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(FONT_SCALING_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.ic_qs_font_scaling,
+                        labelRes = R.string.quick_settings_font_scaling_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject FontScaling Tile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(FONT_SCALING_TILE_SPEC)
+        fun provideFontScalingTileViewModel(
+            factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
+            mapper: FontScalingTileMapper,
+            stateInteractor: FontScalingTileDataInteractor,
+            userActionInteractor: FontScalingTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(FONT_SCALING_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 6076f32..5bd7e54 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -64,8 +64,8 @@
                 }
 
                 override fun onBackProgressed(backEvent: BackEvent) {
-                    if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
-                        shadeViewController.onBackProgressed(backEvent.progress)
+                    if (shouldBackBeHandled() && shadeBackActionInteractor.canBeCollapsed()) {
+                        shadeBackActionInteractor.onBackProgressed(backEvent.progress)
                     }
                 }
             }
@@ -77,12 +77,12 @@
         get() =
             notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
 
-    private lateinit var shadeViewController: ShadeViewController
+    private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
     private lateinit var qsController: QuickSettingsController
 
-    fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+    fun setup(qsController: QuickSettingsController, svController: ShadeBackActionInteractor) {
         this.qsController = qsController
-        this.shadeViewController = svController
+        this.shadeBackActionInteractor = svController
     }
 
     override fun start() {
@@ -114,16 +114,16 @@
             return true
         }
         if (qsController.expanded) {
-            shadeViewController.animateCollapseQs(false)
+            shadeBackActionInteractor.animateCollapseQs(false)
             return true
         }
-        if (shadeViewController.closeUserSwitcherIfOpen()) {
+        if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
             return true
         }
         if (shouldBackBeHandled()) {
-            if (shadeViewController.canBeCollapsed()) {
+            if (shadeBackActionInteractor.canBeCollapsed()) {
                 // this is the Shade dismiss animation, so make sure QQS closes when it ends.
-                shadeViewController.onBackPressed()
+                shadeBackActionInteractor.onBackPressed()
                 shadeController.animateCollapseShade()
             }
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 57e308f..3397906 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.systemui.Flags.constraintBp;
 
 import android.animation.Animator;
 import android.annotation.IntDef;
@@ -57,6 +58,7 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -153,7 +155,7 @@
     @Nullable private Spaghetti mBiometricView;
     @Nullable private View mCredentialView;
     private final AuthPanelController mPanelController;
-    private final FrameLayout mFrameLayout;
+    private final ViewGroup mLayout;
     private final ImageView mBackgroundView;
     private final ScrollView mBiometricScrollView;
     private final View mPanelView;
@@ -339,11 +341,16 @@
         mBiometricCallback = new BiometricCallback();
 
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        mFrameLayout = (FrameLayout) layoutInflater.inflate(
-                R.layout.auth_container_view, this, false /* attachToRoot */);
-        addView(mFrameLayout);
-        mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
-        mBackgroundView = mFrameLayout.findViewById(R.id.background);
+        if (constraintBp()) {
+            mLayout = (ConstraintLayout) layoutInflater.inflate(
+                    R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
+        } else {
+            mLayout = (FrameLayout) layoutInflater.inflate(
+                    R.layout.auth_container_view, this, false /* attachToRoot */);
+        }
+        mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
+        addView(mLayout);
+        mBackgroundView = mLayout.findViewById(R.id.background);
         ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
             @Override
             public void onInitializeAccessibilityNodeInfo(View host,
@@ -358,7 +365,7 @@
             }
         });
 
-        mPanelView = mFrameLayout.findViewById(R.id.panel);
+        mPanelView = mLayout.findViewById(R.id.panel);
         mPanelController = new AuthPanelController(mContext, mPanelView);
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
@@ -402,20 +409,31 @@
                     new BiometricModalities(fpProps, faceProps),
                     config.mOpPackageName);
 
-            final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
-                    R.layout.biometric_prompt_layout, null, false);
-            mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
-                    // TODO(b/201510778): This uses the wrong timeout in some cases
-                    getJankListener(view, TRANSIT,
-                            BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
-                    vibratorHelper);
+            if (constraintBp()) {
+                mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
+                        // TODO(b/201510778): This uses the wrong timeout in some cases
+                        getJankListener(mLayout, TRANSIT,
+                                BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+                        mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                        vibratorHelper);
+            } else {
+                final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
+                        R.layout.biometric_prompt_layout, null, false);
+                mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
+                        // TODO(b/201510778): This uses the wrong timeout in some cases
+                        getJankListener(view, TRANSIT,
+                                BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+                        mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                        vibratorHelper);
 
-            // TODO(b/251476085): migrate these dependencies
-            if (fpProps != null && fpProps.isAnyUdfpsType()) {
-                view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
-                        config.mScaleProvider);
+                // TODO(b/251476085): migrate these dependencies
+                if (fpProps != null && fpProps.isAnyUdfpsType()) {
+                    view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
+                            config.mScaleProvider);
+                }
             }
+        } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
+            addCredentialView(true, false);
         } else {
             mPromptSelectorInteractorProvider.get().resetPrompt();
         }
@@ -477,7 +495,7 @@
         vm.setAnimateContents(animateContents);
         ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
 
-        mFrameLayout.addView(mCredentialView);
+        mLayout.addView(mCredentialView);
     }
 
     @Override
@@ -488,7 +506,9 @@
 
     @Override
     public void onOrientationChanged() {
-        maybeUpdatePositionForUdfps(true /* invalidate */);
+        if (!constraintBp()) {
+            maybeUpdatePositionForUdfps(true /* invalidate */);
+        }
     }
 
     @Override
@@ -502,8 +522,9 @@
         mWakefulnessLifecycle.addObserver(this);
         mPanelInteractionDetector.enable(
                 () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
-
-        if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
+        if (constraintBp()) {
+            // Do nothing on attachment with constraintLayout
+        } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
             mBiometricScrollView.addView(mBiometricView.asView());
         } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
             addCredentialView(true /* animatePanel */, false /* animateContents */);
@@ -512,7 +533,9 @@
                     + mConfig.mPromptInfo.getAuthenticators());
         }
 
-        maybeUpdatePositionForUdfps(false /* invalidate */);
+        if (!constraintBp()) {
+            maybeUpdatePositionForUdfps(false /* invalidate */);
+        }
 
         if (mConfig.mSkipIntro) {
             mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4176083..4ea5f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -198,11 +198,13 @@
                                 UdfpsTouchOverlayBinder.bind(
                                     view = this,
                                     viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+                                    udfpsOverlayInteractor = udfpsOverlayInteractor,
                                 )
                             else ->
                                 UdfpsTouchOverlayBinder.bind(
                                     view = this,
                                     viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+                                    udfpsOverlayInteractor = udfpsOverlayInteractor,
                                 )
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index d28dbc0..27bb023 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -24,17 +24,24 @@
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.biometrics.shared.model.AuthenticationState
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.shareIn
 
 /** A repository for the state of biometric authentication. */
@@ -44,6 +51,9 @@
      * [NotRunning].
      */
     val fingerprintAuthenticationReason: Flow<AuthenticationReason>
+
+    /** The current status of an acquired fingerprint. */
+    val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
 }
 
 @SysUISingleton
@@ -54,53 +64,53 @@
     private val biometricManager: BiometricManager?
 ) : BiometricStatusRepository {
 
-    override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+    private val authenticationState: Flow<AuthenticationState> =
         conflatedCallbackFlow {
-                val updateFingerprintAuthenticateReason = { reason: AuthenticationReason ->
-                    trySendWithFailureLogging(
-                        reason,
-                        TAG,
-                        "Error sending fingerprintAuthenticateReason reason"
-                    )
+                val updateAuthenticationState = { state: AuthenticationState ->
+                    trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
                 }
 
                 val authenticationStateListener =
                     object : AuthenticationStateListener.Stub() {
                         override fun onAuthenticationStarted(requestReason: Int) {
-                            val authenticationReason =
-                                when (requestReason) {
-                                    REASON_AUTH_BP ->
-                                        AuthenticationReason.BiometricPromptAuthentication
-                                    REASON_AUTH_KEYGUARD ->
-                                        AuthenticationReason.DeviceEntryAuthentication
-                                    REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
-                                    REASON_AUTH_SETTINGS ->
-                                        AuthenticationReason.SettingsAuthentication(
-                                            SettingsOperations.OTHER
-                                        )
-                                    REASON_ENROLL_ENROLLING ->
-                                        AuthenticationReason.SettingsAuthentication(
-                                            SettingsOperations.ENROLL_ENROLLING
-                                        )
-                                    REASON_ENROLL_FIND_SENSOR ->
-                                        AuthenticationReason.SettingsAuthentication(
-                                            SettingsOperations.ENROLL_FIND_SENSOR
-                                        )
-                                    else -> AuthenticationReason.Unknown
-                                }
-                            updateFingerprintAuthenticateReason(authenticationReason)
+                            val authenticationReason = requestReason.toAuthenticationReason()
+                            updateAuthenticationState(
+                                AuthenticationState.AuthenticationStarted(authenticationReason)
+                            )
                         }
 
                         override fun onAuthenticationStopped() {
-                            updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+                            updateAuthenticationState(
+                                AuthenticationState.AuthenticationStopped(
+                                    AuthenticationReason.NotRunning
+                                )
+                            )
                         }
 
                         override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
 
                         override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
+
+                        override fun onAuthenticationAcquired(
+                            biometricSourceType: BiometricSourceType,
+                            requestReason: Int,
+                            acquiredInfo: Int
+                        ) {
+                            val authReason = requestReason.toAuthenticationReason()
+
+                            updateAuthenticationState(
+                                AuthenticationState.AuthenticationAcquired(
+                                    biometricSourceType,
+                                    authReason,
+                                    acquiredInfo
+                                )
+                            )
+                        }
                     }
 
-                updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+                updateAuthenticationState(
+                    AuthenticationState.AuthenticationStarted(AuthenticationReason.NotRunning)
+                )
                 biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
                 awaitClose {
                     biometricManager?.unregisterAuthenticationStateListener(
@@ -110,7 +120,36 @@
             }
             .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
 
+    override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+        authenticationState.map { it.requestReason }
+
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        authenticationState
+            .filterIsInstance<AuthenticationState.AuthenticationAcquired>()
+            .filter {
+                it.biometricSourceType == BiometricSourceType.FINGERPRINT &&
+                    // TODO(b/322555228) This check will be removed after consolidating device
+                    //  entry auth messages (currently in DeviceEntryFingerprintAuthRepository)
+                    //  with BP auth messages (here)
+                    it.requestReason == AuthenticationReason.BiometricPromptAuthentication
+            }
+            .map { AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo) }
+
     companion object {
         private const val TAG = "BiometricStatusRepositoryImpl"
     }
 }
+
+private fun Int.toAuthenticationReason(): AuthenticationReason =
+    when (this) {
+        REASON_AUTH_BP -> AuthenticationReason.BiometricPromptAuthentication
+        REASON_AUTH_KEYGUARD -> AuthenticationReason.DeviceEntryAuthentication
+        REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
+        REASON_AUTH_SETTINGS ->
+            AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)
+        REASON_ENROLL_ENROLLING ->
+            AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+        REASON_ENROLL_FIND_SENSOR ->
+            AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR)
+        else -> AuthenticationReason.Unknown
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 792a7ef..c8fb044 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -17,31 +17,20 @@
 package com.android.systemui.biometrics.data.repository
 
 import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DisplayListener
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-import android.os.Handler
 import android.util.Size
 import android.view.DisplayInfo
-import com.android.app.tracing.traceSection
-import com.android.internal.util.ArrayUtils
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.toDisplayRotation
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import java.util.concurrent.Executor
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY
+import com.android.systemui.display.data.repository.DisplayRepository
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -66,52 +55,23 @@
     val currentDisplaySize: StateFlow<Size>
 }
 
-// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
-// instead.
 @SysUISingleton
 class DisplayStateRepositoryImpl
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
+    @Background backgroundScope: CoroutineScope,
     @Application val context: Context,
-    deviceStateManager: DeviceStateManager,
-    displayManager: DisplayManager,
-    @Main handler: Handler,
-    @Background backgroundExecutor: Executor,
-    @Background backgroundDispatcher: CoroutineDispatcher,
+    deviceStateRepository: DeviceStateRepository,
+    displayRepository: DisplayRepository,
 ) : DisplayStateRepository {
     override val isReverseDefaultRotation =
         context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
 
     override val isInRearDisplayMode: StateFlow<Boolean> =
-        conflatedCallbackFlow {
-                val sendRearDisplayStateUpdate = { state: Boolean ->
-                    trySendWithFailureLogging(
-                        state,
-                        TAG,
-                        "Error sending rear display state update to $state"
-                    )
-                }
-
-                val callback =
-                    DeviceStateManager.DeviceStateCallback { state ->
-                        val isInRearDisplayMode =
-                            ArrayUtils.contains(
-                                context.resources.getIntArray(
-                                    com.android.internal.R.array.config_rearDisplayDeviceStates
-                                ),
-                                state
-                            )
-                        sendRearDisplayStateUpdate(isInRearDisplayMode)
-                    }
-
-                sendRearDisplayStateUpdate(false)
-                deviceStateManager.registerCallback(backgroundExecutor, callback)
-                awaitClose { deviceStateManager.unregisterCallback(callback) }
-            }
-            .flowOn(backgroundDispatcher)
+        deviceStateRepository.state
+            .map { it == REAR_DISPLAY }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.Eagerly,
                 initialValue = false,
             )
@@ -123,37 +83,10 @@
     }
 
     private val currentDisplayInfo: StateFlow<DisplayInfo> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : DisplayListener {
-                        override fun onDisplayRemoved(displayId: Int) {}
-
-                        override fun onDisplayAdded(displayId: Int) {}
-
-                        override fun onDisplayChanged(displayId: Int) {
-                            traceSection(
-                                "DisplayStateRepository" +
-                                    ".currentRotationDisplayListener#onDisplayChanged"
-                            ) {
-                                val displayInfo = getDisplayInfo()
-                                trySendWithFailureLogging(
-                                    displayInfo,
-                                    TAG,
-                                    "Error sending displayInfo to $displayInfo"
-                                )
-                            }
-                        }
-                    }
-                displayManager.registerDisplayListener(
-                    callback,
-                    handler,
-                    EVENT_FLAG_DISPLAY_CHANGED
-                )
-                awaitClose { displayManager.unregisterDisplayListener(callback) }
-            }
-            .flowOn(backgroundDispatcher)
+        displayRepository.displayChangeEvent
+            .map { getDisplayInfo() }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.Eagerly,
                 initialValue = getDisplayInfo(),
             )
@@ -170,7 +103,7 @@
         currentDisplayInfo
             .map { rotationToDisplayRotation(it.rotation) }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
             )
@@ -179,7 +112,7 @@
         currentDisplayInfo
             .map { Size(it.naturalWidth, it.naturalHeight) }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     Size(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 55a2d3d..ed1557c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -31,6 +32,9 @@
      * filtered for when the overlay should be shown, otherwise [NotRunning].
      */
     val sfpsAuthenticationReason: Flow<AuthenticationReason>
+
+    /** The current status of an acquired fingerprint. */
+    val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
 }
 
 class BiometricStatusInteractorImpl
@@ -50,6 +54,9 @@
             }
         }
 
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        biometricStatusRepository.fingerprintAcquiredStatus
+
     companion object {
         private const val TAG = "BiometricStatusInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
new file mode 100644
index 0000000..77cf840
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.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.biometrics.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/**
+ * Describes the current state of biometric authentication, including whether authentication is
+ * started, stopped, or acquired and relevant parameters, and the [AuthenticationReason] for
+ * authentication.
+ */
+sealed interface AuthenticationState {
+    val requestReason: AuthenticationReason
+
+    /**
+     * Authentication started
+     *
+     * @param requestReason [AuthenticationReason] for starting authentication
+     */
+    data class AuthenticationStarted(override val requestReason: AuthenticationReason) :
+        AuthenticationState
+
+    /**
+     * Authentication stopped
+     *
+     * @param requestReason [AuthenticationReason.NotRunning]
+     */
+    data class AuthenticationStopped(override val requestReason: AuthenticationReason) :
+        AuthenticationState
+
+    /**
+     * Authentication acquired
+     *
+     * @param biometricSourceType indicates [BiometricSourceType] of acquired authentication
+     * @param requestReason indicates [AuthenticationReason] for requesting auth
+     * @param acquiredInfo indicates
+     */
+    data class AuthenticationAcquired(
+        val biometricSourceType: BiometricSourceType,
+        override val requestReason: AuthenticationReason,
+        val acquiredInfo: Int
+    ) : AuthenticationState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 285ab4a..efad21b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -41,6 +41,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieCompositionFactory
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -70,9 +71,9 @@
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
-        view: BiometricPromptLayout,
+        view: View,
         viewModel: PromptViewModel,
-        panelViewController: AuthPanelController,
+        panelViewController: AuthPanelController?,
         jankListener: BiometricJankListener,
         backgroundView: View,
         legacyCallback: Spaghetti.Callback,
@@ -112,11 +113,18 @@
         val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
 
+        val iconSizeOverride =
+            if (constraintBp()) {
+                viewModel.fingerprintAffordanceSize
+            } else {
+                (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
+            }
+
         PromptIconViewBinder.bind(
             iconView,
             iconOverlayView,
-            view.getUpdatedFingerprintAffordanceSize(),
-            viewModel
+            iconSizeOverride,
+            viewModel,
         )
 
         val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d5695f3..2417fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,29 +19,45 @@
 import android.animation.Animator
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
+import android.graphics.Outline
+import android.graphics.Rect
+import android.transition.AutoTransition
+import android.transition.TransitionManager
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewOutlineProvider
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.Guideline
 import androidx.core.animation.addListener
+import androidx.core.view.doOnAttach
 import androidx.core.view.doOnLayout
 import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.biometrics.ui.viewmodel.isBottom
 import com.android.systemui.biometrics.ui.viewmodel.isLarge
+import com.android.systemui.biometrics.ui.viewmodel.isLeft
 import com.android.systemui.biometrics.ui.viewmodel.isMedium
 import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
+import com.android.systemui.biometrics.ui.viewmodel.isRight
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
+import com.android.systemui.biometrics.ui.viewmodel.isTop
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
+import kotlin.math.abs
+import kotlin.math.min
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
@@ -54,18 +70,19 @@
 
     /** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */
     fun bind(
-        view: BiometricPromptLayout,
+        view: View,
         viewModel: PromptViewModel,
         viewsToHideWhenSmall: List<View>,
         viewsToFadeInOnSizeChange: List<View>,
-        panelViewController: AuthPanelController,
+        panelViewController: AuthPanelController?,
         jankListener: BiometricJankListener,
     ) {
         val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
         val accessibilityManager =
             requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
+
         fun notifyAccessibilityChanged() {
-            Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
+            Utils.notifyAccessibilityContentChanged(accessibilityManager, view as ViewGroup)
         }
 
         fun startMonitoredAnimation(animators: List<Animator>) {
@@ -77,149 +94,342 @@
             }
         }
 
-        val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
-        val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
-        val fullSizeYOffset =
-            view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
+        if (constraintBp()) {
+            val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
+            val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
+            val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
 
-        // cache the original position of the icon view (as done in legacy view)
-        // this must happen before any size changes can be made
-        view.doOnLayout {
-            // TODO(b/251476085): this old way of positioning has proven itself unreliable
-            // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
-            // pin to the physical sensor
-            val iconHolderOriginalY = iconHolderView.y
+            val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
+            val panelView = view.requireViewById<View>(R.id.panel)
+            val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
 
-            // bind to prompt
-            // TODO(b/251476085): migrate the legacy panel controller and simplify this
-            view.repeatWhenAttached {
-                var currentSize: PromptSize? = null
-                lifecycleScope.launch {
-                    /**
-                     * View is only set visible in BiometricViewSizeBinder once PromptSize is
-                     * determined that accounts for iconView size, to prevent prompt resizing being
-                     * visible to the user.
-                     *
-                     * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
-                     *   layout is implemented
-                     */
-                    combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
-                        (isIconViewLoaded, size) ->
-                        if (!isIconViewLoaded) {
-                            return@collect
+            // ConstraintSets for animating between prompt sizes
+            val mediumConstraintSet = ConstraintSet()
+            mediumConstraintSet.clone(view as ConstraintLayout)
+
+            val smallConstraintSet = ConstraintSet()
+            smallConstraintSet.clone(mediumConstraintSet)
+            viewsToHideWhenSmall.forEach { smallConstraintSet.setVisibility(it.id, View.GONE) }
+
+            val largeConstraintSet = ConstraintSet()
+            largeConstraintSet.clone(mediumConstraintSet)
+            viewsToHideWhenSmall.forEach { largeConstraintSet.setVisibility(it.id, View.GONE) }
+            largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+            largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+            largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+            largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
+            largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
+            largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
+
+            // Round the panel outline
+            panelView.outlineProvider =
+                object : ViewOutlineProvider() {
+                    override fun getOutline(view: View, outline: Outline) {
+                        outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+                    }
+                }
+
+            view.doOnLayout {
+                val windowBounds = windowManager.maximumWindowMetrics.bounds
+                val bottomInset =
+                    windowManager.maximumWindowMetrics.windowInsets
+                        .getInsets(WindowInsets.Type.navigationBars())
+                        .bottom
+
+                fun measureBounds(position: PromptPosition) {
+                    val width = min(windowBounds.height(), windowBounds.width())
+
+                    var left = -1
+                    var top = -1
+                    var right = -1
+                    var bottom = -1
+
+                    when {
+                        position.isTop -> {
+                            left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                            top = viewModel.promptMargin
+                            right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                            bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
+                        }
+                        position.isBottom -> {
+                            if (view.isLandscape()) {
+                                left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                top = iconHolderView.centerY()
+                                right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                bottom = bottomInset + viewModel.promptMargin
+                            } else {
+                                left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                top =
+                                    windowBounds.height() -
+                                        (windowBounds.height() - iconHolderView.centerY()) * 2 +
+                                        viewModel.promptMargin
+                                right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                bottom = viewModel.promptMargin
+                            }
                         }
 
-                        // prepare for animated size transitions
-                        for (v in viewsToHideWhenSmall) {
-                            v.showContentOrHide(forceHide = size.isSmall)
+                        // For Udfps exclusive left and right, measure guideline to center
+                        // icon in BP
+                        position.isLeft -> {
+                            left = viewModel.promptMargin
+                            top =
+                                windowBounds.height() -
+                                    (windowBounds.height() - iconHolderView.centerY()) * 2 +
+                                    viewModel.promptMargin
+                            right =
+                                abs(
+                                    windowBounds.width() - iconHolderView.centerX() * 2 +
+                                        viewModel.promptMargin
+                                )
+                            bottom = bottomInset + viewModel.promptMargin
                         }
-                        if (currentSize == null && size.isSmall) {
-                            iconHolderView.alpha = 0f
+                        position.isRight -> {
+                            left =
+                                abs(
+                                    iconHolderView.centerX() -
+                                        (windowBounds.width() - iconHolderView.centerX()) -
+                                        viewModel.promptMargin
+                                )
+                            top =
+                                windowBounds.height() -
+                                    (windowBounds.height() - iconHolderView.centerY()) * 2 +
+                                    viewModel.promptMargin
+                            right = viewModel.promptMargin
+                            bottom = bottomInset + viewModel.promptMargin
                         }
-                        if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
-                            viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
-                        }
+                    }
 
-                        // TODO(b/302735104): Fix wrong height due to the delay of
-                        // PromptContentView. addOnLayoutChangeListener() will cause crash when
-                        // showing credential view, since |PromptIconViewModel| won't release the
-                        // flow.
-                        // propagate size changes to legacy panel controller and animate transitions
-                        view.doOnLayout {
-                            val width = view.measuredWidth
-                            val height = view.measuredHeight
+                    val bounds = Rect(left, top, right, bottom)
+                    if (bounds.shouldAdjustLeftGuideline()) {
+                        leftGuideline.setGuidelineBegin(bounds.left)
+                        smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                        mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                    }
+                    if (bounds.shouldAdjustRightGuideline()) {
+                        rightGuideline.setGuidelineEnd(bounds.right)
+                        smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                        mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                    }
+                    if (bounds.shouldAdjustBottomGuideline()) {
+                        bottomGuideline.setGuidelineEnd(bounds.bottom)
+                        smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+                        mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+                    }
+                }
 
-                            when {
-                                size.isSmall -> {
-                                    iconHolderView.alpha = 1f
-                                    val bottomInset =
-                                        windowManager.maximumWindowMetrics.windowInsets
-                                            .getInsets(WindowInsets.Type.navigationBars())
-                                            .bottom
-                                    iconHolderView.y =
-                                        if (view.isLandscape()) {
-                                            (view.height - iconHolderView.height - bottomInset) / 2f
-                                        } else {
-                                            view.height -
-                                                iconHolderView.height -
-                                                iconPadding -
-                                                bottomInset
-                                        }
-                                    val newHeight =
-                                        iconHolderView.height + (2 * iconPadding.toInt()) -
-                                            iconHolderView.paddingTop -
-                                            iconHolderView.paddingBottom
-                                    panelViewController.updateForContentDimensions(
-                                        width,
-                                        newHeight + bottomInset,
-                                        0, /* animateDurationMs */
-                                    )
-                                }
-                                size.isMedium && currentSize.isSmall -> {
-                                    val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
-                                    panelViewController.updateForContentDimensions(
-                                        width,
-                                        height,
-                                        duration,
-                                    )
-                                    startMonitoredAnimation(
-                                        listOf(
-                                            iconHolderView.asVerticalAnimator(
-                                                duration = duration.toLong(),
-                                                toY =
-                                                    iconHolderOriginalY -
-                                                        viewsToHideWhenSmall
-                                                            .filter { it.isGone }
-                                                            .sumOf { it.height },
-                                            ),
-                                            viewsToFadeInOnSizeChange.asFadeInAnimator(
-                                                duration = duration.toLong(),
-                                                delay = duration.toLong(),
-                                            ),
+                view.repeatWhenAttached {
+                    var currentSize: PromptSize? = null
+                    lifecycleScope.launch {
+                        combine(viewModel.position, viewModel.size, ::Pair).collect {
+                            (position, size) ->
+                            view.doOnAttach {
+                                measureBounds(position)
+
+                                when {
+                                    size.isSmall -> {
+                                        val ratio =
+                                            if (view.isLandscape()) {
+                                                (windowBounds.height() -
+                                                        bottomInset -
+                                                        viewModel.promptMargin)
+                                                    .toFloat() / windowBounds.height()
+                                            } else {
+                                                (windowBounds.height() - viewModel.promptMargin)
+                                                    .toFloat() / windowBounds.height()
+                                            }
+                                        smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
+
+                                        smallConstraintSet.applyTo(view as ConstraintLayout?)
+                                    }
+                                    size.isMedium && currentSize.isSmall -> {
+                                        val autoTransition = AutoTransition()
+                                        autoTransition.setDuration(
+                                            ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
                                         )
-                                    )
-                                }
-                                size.isMedium && currentSize.isNullOrNotSmall -> {
-                                    panelViewController.updateForContentDimensions(
-                                        width,
-                                        height,
-                                        0, /* animateDurationMs */
-                                    )
-                                }
-                                size.isLarge -> {
-                                    val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
-                                    panelViewController.setUseFullScreen(true)
-                                    panelViewController.updateForContentDimensions(
-                                        panelViewController.containerWidth,
-                                        panelViewController.containerHeight,
-                                        duration,
-                                    )
 
-                                    startMonitoredAnimation(
-                                        listOf(
-                                            view.asVerticalAnimator(
-                                                duration.toLong() * 2 / 3,
-                                                toY = view.y - fullSizeYOffset
-                                            ),
-                                            listOf(view)
-                                                .asFadeInAnimator(
-                                                    duration = duration.toLong() / 2,
-                                                    delay = duration.toLong(),
-                                                ),
+                                        TransitionManager.beginDelayedTransition(
+                                            view,
+                                            autoTransition
                                         )
-                                    )
-                                    // TODO(b/251476085): clean up (copied from legacy)
-                                    if (view.isAttachedToWindow) {
-                                        val parent = view.parent as? ViewGroup
-                                        parent?.removeView(view)
+                                        mediumConstraintSet.applyTo(view)
+                                    }
+                                    size.isLarge -> {
+                                        val autoTransition = AutoTransition()
+                                        autoTransition.setDuration(
+                                            ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+                                        )
+
+                                        TransitionManager.beginDelayedTransition(
+                                            view,
+                                            autoTransition
+                                        )
+                                        largeConstraintSet.applyTo(view)
                                     }
                                 }
+
+                                currentSize = size
+                                view.visibility = View.VISIBLE
+                                viewModel.setIsIconViewLoaded(false)
+                                notifyAccessibilityChanged()
+
+                                view.invalidate()
+                                view.requestLayout()
+                            }
+                        }
+                    }
+                }
+            }
+        } else if (panelViewController != null) {
+            val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
+            val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
+            val fullSizeYOffset =
+                view.resources.getDimension(
+                    R.dimen.biometric_dialog_medium_to_large_translation_offset
+                )
+
+            // cache the original position of the icon view (as done in legacy view)
+            // this must happen before any size changes can be made
+            view.doOnLayout {
+                // TODO(b/251476085): this old way of positioning has proven itself unreliable
+                // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+                // pin to the physical sensor
+                val iconHolderOriginalY = iconHolderView.y
+
+                // bind to prompt
+                // TODO(b/251476085): migrate the legacy panel controller and simplify this
+                view.repeatWhenAttached {
+                    var currentSize: PromptSize? = null
+                    lifecycleScope.launch {
+                        /**
+                         * View is only set visible in BiometricViewSizeBinder once PromptSize is
+                         * determined that accounts for iconView size, to prevent prompt resizing
+                         * being visible to the user.
+                         *
+                         * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+                         *   layout is implemented
+                         */
+                        combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+                            (isIconViewLoaded, size) ->
+                            if (!isIconViewLoaded) {
+                                return@collect
                             }
 
-                            currentSize = size
-                            view.visibility = View.VISIBLE
-                            viewModel.setIsIconViewLoaded(false)
-                            notifyAccessibilityChanged()
+                            // prepare for animated size transitions
+                            for (v in viewsToHideWhenSmall) {
+                                v.showContentOrHide(forceHide = size.isSmall)
+                            }
+                            if (currentSize == null && size.isSmall) {
+                                iconHolderView.alpha = 0f
+                            }
+                            if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
+                                viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
+                            }
+
+                            // TODO(b/302735104): Fix wrong height due to the delay of
+                            // PromptContentView. addOnLayoutChangeListener() will cause crash when
+                            // showing credential view, since |PromptIconViewModel| won't release
+                            // the
+                            // flow.
+                            // propagate size changes to legacy panel controller and animate
+                            // transitions
+                            view.doOnLayout {
+                                val width = view.measuredWidth
+                                val height = view.measuredHeight
+
+                                when {
+                                    size.isSmall -> {
+                                        iconHolderView.alpha = 1f
+                                        val bottomInset =
+                                            windowManager.maximumWindowMetrics.windowInsets
+                                                .getInsets(WindowInsets.Type.navigationBars())
+                                                .bottom
+                                        iconHolderView.y =
+                                            if (view.isLandscape()) {
+                                                (view.height -
+                                                    iconHolderView.height -
+                                                    bottomInset) / 2f
+                                            } else {
+                                                view.height -
+                                                    iconHolderView.height -
+                                                    iconPadding -
+                                                    bottomInset
+                                            }
+                                        val newHeight =
+                                            iconHolderView.height + (2 * iconPadding.toInt()) -
+                                                iconHolderView.paddingTop -
+                                                iconHolderView.paddingBottom
+                                        panelViewController.updateForContentDimensions(
+                                            width,
+                                            newHeight + bottomInset,
+                                            0, /* animateDurationMs */
+                                        )
+                                    }
+                                    size.isMedium && currentSize.isSmall -> {
+                                        val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
+                                        panelViewController.updateForContentDimensions(
+                                            width,
+                                            height,
+                                            duration,
+                                        )
+                                        startMonitoredAnimation(
+                                            listOf(
+                                                iconHolderView.asVerticalAnimator(
+                                                    duration = duration.toLong(),
+                                                    toY =
+                                                        iconHolderOriginalY -
+                                                            viewsToHideWhenSmall
+                                                                .filter { it.isGone }
+                                                                .sumOf { it.height },
+                                                ),
+                                                viewsToFadeInOnSizeChange.asFadeInAnimator(
+                                                    duration = duration.toLong(),
+                                                    delay = duration.toLong(),
+                                                ),
+                                            )
+                                        )
+                                    }
+                                    size.isMedium && currentSize.isNullOrNotSmall -> {
+                                        panelViewController.updateForContentDimensions(
+                                            width,
+                                            height,
+                                            0, /* animateDurationMs */
+                                        )
+                                    }
+                                    size.isLarge -> {
+                                        val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
+                                        panelViewController.setUseFullScreen(true)
+                                        panelViewController.updateForContentDimensions(
+                                            panelViewController.containerWidth,
+                                            panelViewController.containerHeight,
+                                            duration,
+                                        )
+
+                                        startMonitoredAnimation(
+                                            listOf(
+                                                view.asVerticalAnimator(
+                                                    duration.toLong() * 2 / 3,
+                                                    toY = view.y - fullSizeYOffset
+                                                ),
+                                                listOf(view)
+                                                    .asFadeInAnimator(
+                                                        duration = duration.toLong() / 2,
+                                                        delay = duration.toLong(),
+                                                    ),
+                                            )
+                                        )
+                                        // TODO(b/251476085): clean up (copied from legacy)
+                                        if (view.isAttachedToWindow) {
+                                            val parent = view.parent as? ViewGroup
+                                            parent?.removeView(view)
+                                        }
+                                    }
+                                }
+
+                                currentSize = size
+                                view.visibility = View.VISIBLE
+                                viewModel.setIsIconViewLoaded(false)
+                                notifyAccessibilityChanged()
+                            }
                         }
                     }
                 }
@@ -244,6 +454,20 @@
         }
 }
 
+private fun View.centerX(): Int {
+    return (x + width / 2).toInt()
+}
+
+private fun View.centerY(): Int {
+    return (y + height / 2).toInt()
+}
+
+private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
+
+private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
+
+private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
+
 private fun View.asVerticalAnimator(
     duration: Long,
     toY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 6e3bcf5..2e47375 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -17,13 +17,17 @@
 
 package com.android.systemui.biometrics.ui.binder
 
+import android.graphics.Rect
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
 import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
@@ -119,6 +123,24 @@
                 }
 
                 launch {
+                    viewModel.iconPosition.collect { position ->
+                        if (constraintBp() && position != Rect()) {
+                            val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
+
+                            if (position.left != -1) {
+                                iconParams.endToEnd = ConstraintSet.UNSET
+                                iconParams.leftMargin = position.left
+                            }
+                            if (position.top != -1) {
+                                iconParams.bottomToBottom = ConstraintSet.UNSET
+                                iconParams.topMargin = position.top
+                            }
+                            iconView.layoutParams = iconParams
+                        }
+                    }
+                }
+
+                launch {
                     viewModel.iconAsset
                         .sample(
                             combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 80d37b4..7b4be02 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -50,10 +50,12 @@
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SideFpsOverlayViewBinder
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index bb6a68b..2e29c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.biometrics.ui.binder
 
+import android.util.Log
 import androidx.core.view.isInvisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
 import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -31,19 +33,26 @@
 
     /**
      * Updates visibility for the UdfpsTouchOverlay which controls whether the view will receive
-     * touches or not.
+     * touches or not. For some devices, this is instead handled by UdfpsOverlayInteractor, so this
+     * viewBinder will send the information to the interactor.
      */
     @JvmStatic
     fun bind(
         view: UdfpsTouchOverlay,
         viewModel: UdfpsTouchOverlayViewModel,
+        udfpsOverlayInteractor: UdfpsOverlayInteractor,
     ) {
         if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
                     viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+                        Log.d(
+                            "UdfpsTouchOverlayBinder",
+                            "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+                        )
                         view.isInvisible = !shouldHandleTouches
+                        udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 3defec5..b7cffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -20,8 +20,11 @@
 import android.annotation.DrawableRes
 import android.annotation.RawRes
 import android.content.res.Configuration
+import android.graphics.Rect
+import android.util.RotationUtils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.res.R
@@ -42,7 +45,8 @@
 constructor(
     promptViewModel: PromptViewModel,
     private val displayStateInteractor: DisplayStateInteractor,
-    promptSelectorInteractor: PromptSelectorInteractor
+    promptSelectorInteractor: PromptSelectorInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
 
     /** Auth types for the UI to display. */
@@ -71,7 +75,40 @@
             } else if (modalities.hasFingerprintOnly) {
                 AuthType.Fingerprint
             } else {
-                throw IllegalStateException("unexpected modality: $modalities")
+                // TODO(b/288175072): Remove, currently needed for transition to credential view
+                AuthType.Fingerprint
+            }
+        }
+
+    val udfpsSensorBounds: Flow<Rect> =
+        combine(
+                udfpsOverlayInteractor.udfpsOverlayParams,
+                displayStateInteractor.currentRotation
+            ) { params, rotation ->
+                val rotatedBounds = Rect(params.sensorBounds)
+                RotationUtils.rotateBounds(
+                    rotatedBounds,
+                    params.naturalDisplayWidth,
+                    params.naturalDisplayHeight,
+                    rotation.ordinal
+                )
+                rotatedBounds
+            }
+            .distinctUntilChanged()
+
+    val iconPosition: Flow<Rect> =
+        combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
+            sensorBounds,
+            size,
+            modalities ->
+            // If not Udfps, icon does not change from default layout position
+            if (!modalities.hasUdfps) {
+                Rect() // Empty rect, don't offset from default position
+            } else if (size.isSmall) {
+                // When small with Udfps, only set horizontal position
+                Rect(sensorBounds.left, -1, sensorBounds.right, -1)
+            } else {
+                sensorBounds
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt
new file mode 100644
index 0000000..d45dad6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.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.biometrics.ui.viewmodel
+
+/** The position of a biometric prompt */
+enum class PromptPosition {
+    Top,
+    Bottom,
+    Left,
+    Right,
+}
+
+val PromptPosition?.isBottom: Boolean
+    get() = this != null && this == PromptPosition.Bottom
+
+val PromptPosition?.isLeft: Boolean
+    get() = this != null && this == PromptPosition.Left
+
+val PromptPosition?.isRight: Boolean
+    get() = this != null && this == PromptPosition.Right
+
+val PromptPosition?.isTop: Boolean
+    get() = this != null && this == PromptPosition.Top
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 0f1340a..ef5c37ea 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.Context
+import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
@@ -81,11 +82,23 @@
     val faceIconHeight: Int =
         context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
+    val fingerprintSensorDiameter: Int =
+        (udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width() *
+                udfpsOverlayInteractor.udfpsOverlayParams.value.scaleFactor)
+            .toInt()
+    val fingerprintAffordanceSize: Pair<Int, Int>? =
+        if (fingerprintSensorDiameter != 0)
+            Pair(fingerprintSensorDiameter, fingerprintSensorDiameter)
+        else null
+
     private val _accessibilityHint = MutableSharedFlow<String>()
 
     /** Hint for talkback directional guidance */
     val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
 
+    val promptMargin: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -135,6 +148,22 @@
     /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
     val hapticsToPlay = _hapticsToPlay.asStateFlow()
 
+    /** The current position of the prompt */
+    val position: Flow<PromptPosition> =
+        combine(_forceLargeSize, modalities, displayStateInteractor.currentRotation) {
+                forceLarge,
+                modalities,
+                rotation ->
+                when {
+                    forceLarge || !modalities.hasUdfps -> PromptPosition.Bottom
+                    rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
+                    rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
+                    rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top
+                    else -> PromptPosition.Bottom
+                }
+            }
+            .distinctUntilChanged()
+
     /** The size of the prompt. */
     val size: Flow<PromptSize> =
         combine(
@@ -195,7 +224,12 @@
             .distinctUntilChanged()
 
     val iconViewModel: PromptIconViewModel =
-        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+        PromptIconViewModel(
+            this,
+            displayStateInteractor,
+            promptSelectorInteractor,
+            udfpsOverlayInteractor
+        )
 
     private val _isIconViewLoaded = MutableStateFlow(false)
 
@@ -244,7 +278,13 @@
                     !customBiometricPrompt() || it == null -> null
                     it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
                     it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
-                    else -> context.packageManager.getApplicationIcon(it.opPackageName)
+                    else ->
+                        try {
+                            context.packageManager.getApplicationIcon(it.opPackageName)
+                        } catch (e: PackageManager.NameNotFoundException) {
+                            Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
+                            null
+                        }
                 }
             }
             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index ce72603..cfda75c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -41,12 +41,14 @@
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 
 /** Models UI of the side fingerprint sensor indicator view. */
+@OptIn(ExperimentalCoroutinesApi::class)
 class SideFpsOverlayViewModel
 @Inject
 constructor(
@@ -176,8 +178,8 @@
     val lottieCallbacks: Flow<List<LottieCallback>> =
         combine(
             biometricStatusInteractor.sfpsAuthenticationReason,
-            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(),
-            sideFpsProgressBarViewModel.isVisible,
+            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+            sideFpsProgressBarViewModel.isVisible
         ) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible
             ->
             val callbacks = mutableListOf<LottieCallback>()
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 11c7a31..ecbd3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -69,7 +69,7 @@
         }
 
         val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
-            getStartCameraIntent(),
+            getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
             PackageManager.MATCH_DEFAULT_ONLY,
             selectedUserInteractor.getSelectedUserId()
         )
@@ -85,7 +85,7 @@
      * @param source The source of the camera launch, to be passed to the camera app via [Intent]
      */
     fun launchCamera(source: Int) {
-        val intent: Intent = getStartCameraIntent()
+        val intent: Intent = getStartCameraIntent(selectedUserInteractor.getSelectedUserId())
         intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
         val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
             intent, selectedUserInteractor.getSelectedUserId()
@@ -143,13 +143,13 @@
      * Returns an [Intent] that can be used to start the camera app such that it occludes the
      * lock-screen, if needed.
      */
-    private fun getStartCameraIntent(): Intent {
+    private fun getStartCameraIntent(userId: Int): Intent {
         val isLockScreenDismissible = keyguardStateController.canDismissLockScreen()
         val isSecure = keyguardStateController.isMethodSecure
         return if (isSecure && !isLockScreenDismissible) {
-            cameraIntents.getSecureCameraIntent()
+            cameraIntents.getSecureCameraIntent(userId)
         } else {
-            cameraIntents.getInsecureCameraIntent()
+            cameraIntents.getInsecureCameraIntent(userId)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 1e17059..1137586 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -18,9 +18,11 @@
 
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.provider.MediaStore
 import android.text.TextUtils
 import com.android.systemui.res.R
+import android.util.Log
 
 class CameraIntents {
     companion object {
@@ -28,28 +30,33 @@
         val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
         private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
         const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
+        const val TAG = "CameraIntents"
 
         @JvmStatic
-        fun getOverrideCameraPackage(context: Context): String? {
-            context.resources.getString(R.string.config_cameraGesturePackage)?.let {
-                if (!TextUtils.isEmpty(it)) {
-                    return it
+        fun getOverrideCameraPackage(context: Context, userId: Int): String? {
+            val packageName = context.resources.getString(R.string.config_cameraGesturePackage)!!
+            try {
+                if (!TextUtils.isEmpty(packageName)
+                        && context.packageManager.getApplicationInfoAsUser(packageName, 0, userId).enabled ?: false) {
+                    return packageName
                 }
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.w(TAG, "Missing cameraGesturePackage $packageName", e)
             }
             return null
         }
 
         @JvmStatic
-        fun getInsecureCameraIntent(context: Context): Intent {
+        fun getInsecureCameraIntent(context: Context, userId: Int): Intent {
             val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
-            getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
+            getOverrideCameraPackage(context, userId)?.let { intent.setPackage(it) }
             return intent
         }
 
         @JvmStatic
-        fun getSecureCameraIntent(context: Context): Intent {
+        fun getSecureCameraIntent(context: Context, userId: Int): Intent {
             val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
-            getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
+            getOverrideCameraPackage(context, userId)?.let { intent.setPackage(it) }
             return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
         }
 
@@ -65,7 +72,7 @@
 
         /** Returns an [Intent] that can be used to start the camera in video mode. */
         @JvmStatic
-        fun getVideoCameraIntent(): Intent {
+        fun getVideoCameraIntent(userId: Int): Intent {
             return Intent(VIDEO_CAMERA_INTENT_ACTION)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index a434617..b65c0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -29,22 +29,22 @@
 
     /**
      * Returns an [Intent] that can be used to start the camera, suitable for when the device is
-     * already unlocked
+     * locked
      */
-    fun getSecureCameraIntent(): Intent {
-        return CameraIntents.getSecureCameraIntent(context)
+    fun getSecureCameraIntent(userId: Int): Intent {
+        return CameraIntents.getSecureCameraIntent(context, userId)
     }
 
     /**
      * Returns an [Intent] that can be used to start the camera, suitable for when the device is not
      * already unlocked
      */
-    fun getInsecureCameraIntent(): Intent {
-        return CameraIntents.getInsecureCameraIntent(context)
+    fun getInsecureCameraIntent(userId: Int): Intent {
+        return CameraIntents.getInsecureCameraIntent(context, userId)
     }
 
     /** Returns an [Intent] that can be used to start the camera in video mode. */
-    fun getVideoCameraIntent(): Intent {
-        return CameraIntents.getVideoCameraIntent()
+    fun getVideoCameraIntent(userId: Int): Intent {
+        return CameraIntents.getVideoCameraIntent(userId)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
index 7c7b3db..5c64dc6 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.common.data.repository
 
 import android.os.UserHandle
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import kotlinx.coroutines.flow.Flow
 
 interface PackageChangeRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
index b1b348c..712a352 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.common.data.repository
 
 import android.os.UserHandle
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -36,7 +36,5 @@
     private val monitor by lazy { monitorFactory.create(UserHandle.ALL) }
 
     override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> =
-        monitor.packageChanged.filter {
-            user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
-        }
+        monitor.packageChanged.filter { user == UserHandle.ALL || user == it.user }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
index 4184ef7..cba2b47 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.common.data.repository
 
 import android.os.UserHandle
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
index f7cc344..3fed69e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
@@ -20,9 +20,10 @@
 import android.os.Handler
 import android.os.UserHandle
 import com.android.internal.content.PackageMonitor
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.time.SystemClock
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -51,6 +52,7 @@
     @Application private val context: Context,
     @Application private val scope: CoroutineScope,
     private val logger: PackageUpdateLogger,
+    private val systemClock: SystemClock,
 ) : PackageMonitor() {
 
     @AssistedFactory
@@ -88,12 +90,24 @@
 
     override fun onPackageAdded(packageName: String, uid: Int) {
         super.onPackageAdded(packageName, uid)
-        _packageChanged.tryEmit(PackageChangeModel.Installed(packageName, uid))
+        _packageChanged.tryEmit(
+            PackageChangeModel.Installed(
+                packageName = packageName,
+                packageUid = uid,
+                timeMillis = systemClock.currentTimeMillis()
+            )
+        )
     }
 
     override fun onPackageRemoved(packageName: String, uid: Int) {
         super.onPackageRemoved(packageName, uid)
-        _packageChanged.tryEmit(PackageChangeModel.Uninstalled(packageName, uid))
+        _packageChanged.tryEmit(
+            PackageChangeModel.Uninstalled(
+                packageName = packageName,
+                packageUid = uid,
+                timeMillis = systemClock.currentTimeMillis()
+            )
+        )
     }
 
     override fun onPackageChanged(
@@ -102,18 +116,36 @@
         components: Array<out String>
     ): Boolean {
         super.onPackageChanged(packageName, uid, components)
-        _packageChanged.tryEmit(PackageChangeModel.Changed(packageName, uid))
+        _packageChanged.tryEmit(
+            PackageChangeModel.Changed(
+                packageName = packageName,
+                packageUid = uid,
+                timeMillis = systemClock.currentTimeMillis()
+            )
+        )
         return false
     }
 
     override fun onPackageUpdateStarted(packageName: String, uid: Int) {
         super.onPackageUpdateStarted(packageName, uid)
-        _packageChanged.tryEmit(PackageChangeModel.UpdateStarted(packageName, uid))
+        _packageChanged.tryEmit(
+            PackageChangeModel.UpdateStarted(
+                packageName = packageName,
+                packageUid = uid,
+                timeMillis = systemClock.currentTimeMillis()
+            )
+        )
     }
 
     override fun onPackageUpdateFinished(packageName: String, uid: Int) {
         super.onPackageUpdateFinished(packageName, uid)
-        _packageChanged.tryEmit(PackageChangeModel.UpdateFinished(packageName, uid))
+        _packageChanged.tryEmit(
+            PackageChangeModel.UpdateFinished(
+                packageName = packageName,
+                packageUid = uid,
+                timeMillis = systemClock.currentTimeMillis()
+            )
+        )
     }
 
     private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
deleted file mode 100644
index 3ae87c3..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
+++ /dev/null
@@ -1,79 +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.common.data.shared.model
-
-import android.content.Intent
-
-/** Represents changes to an installed package. */
-sealed interface PackageChangeModel {
-    val packageName: String
-    val packageUid: Int
-
-    /** Empty change, provided for convenience when a sensible default value is needed. */
-    data object Empty : PackageChangeModel {
-        override val packageName: String
-            get() = ""
-        override val packageUid: Int
-            get() = 0
-    }
-
-    /**
-     * An existing application package was uninstalled.
-     *
-     * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
-     * [Intent.EXTRA_REPLACING] set to false.
-     */
-    data class Uninstalled(override val packageName: String, override val packageUid: Int) :
-        PackageChangeModel
-
-    /**
-     * A new version of an existing application is going to be installed.
-     *
-     * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
-     * [Intent.EXTRA_REPLACING] set to true.
-     */
-    data class UpdateStarted(override val packageName: String, override val packageUid: Int) :
-        PackageChangeModel
-
-    /**
-     * A new version of an existing application package has been installed, replacing the old
-     * version.
-     *
-     * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
-     * [Intent.EXTRA_REPLACING] set to true.
-     */
-    data class UpdateFinished(override val packageName: String, override val packageUid: Int) :
-        PackageChangeModel
-
-    /**
-     * A new application package has been installed.
-     *
-     * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
-     * [Intent.EXTRA_REPLACING] set to false.
-     */
-    data class Installed(override val packageName: String, override val packageUid: Int) :
-        PackageChangeModel
-
-    /**
-     * An existing application package has been changed (for example, a component has been enabled
-     * or disabled).
-     *
-     * Equivalent to receiving the [Intent.ACTION_PACKAGE_CHANGED] broadcast.
-     */
-    data class Changed(override val packageName: String, override val packageUid: Int) :
-        PackageChangeModel
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/PackageChangeInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/PackageChangeInteractor.kt
new file mode 100644
index 0000000..59c1923
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/PackageChangeInteractor.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Allows listening to package updates. This is recommended over registering broadcasts directly as
+ * it avoids the delay imposed by broadcasts, and provides more structured updates.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class PackageChangeInteractor
+@Inject
+constructor(
+    private val packageChangeRepository: PackageChangeRepository,
+    private val userInteractor: SelectedUserInteractor,
+) {
+    /**
+     * Emits values when packages for the specified user are changed. See supported modifications in
+     * [PackageChangeModel]
+     *
+     * @param user The user to listen to. [UserHandle.USER_ALL] may be used to listen to all users.
+     *   [UserHandle.USER_CURRENT] can be used to listen to the currently active user, and
+     *   automatically handles user switching.
+     * @param packageName An optional package name to filter updates by. If not specified, will
+     *   receive updates for all packages.
+     */
+    fun packageChanged(user: UserHandle, packageName: String? = null): Flow<PackageChangeModel> {
+        if (user == UserHandle.CURRENT) {
+            return userInteractor.selectedUser.flatMapLatest { userId ->
+                packageChangedInternal(UserHandle.of(userId), packageName)
+            }
+        }
+        return packageChangedInternal(user, packageName)
+    }
+
+    private fun packageChangedInternal(
+        user: UserHandle,
+        packageName: String?,
+    ): Flow<PackageChangeModel> =
+        packageChangeRepository.packageChanged(user).filter { model ->
+            (model.packageName == (packageName ?: model.packageName))
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageChangeModel.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageChangeModel.kt
new file mode 100644
index 0000000..bcb00fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageChangeModel.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.common.shared.model
+
+import android.annotation.CurrentTimeMillisLong
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
+
+/** Represents changes to an installed package. */
+sealed interface PackageChangeModel {
+    /** The package which this change corresponds to, eg "com.android.systemui". */
+    val packageName: String
+
+    /**
+     * The uid for this package, which uniquely identifies this package and user. The same package
+     * running on different users will have differing uids.
+     */
+    val packageUid: Int
+
+    /**
+     * The time in milliseconds that this update was received from [PackageManager].
+     *
+     * @see System.currentTimeMillis()
+     */
+    @get:CurrentTimeMillisLong val timeMillis: Long
+
+    /** The user from which this change originated. */
+    val user: UserHandle
+        get() = UserHandle.getUserHandleForUid(packageUid)
+
+    /** Empty change, provided for convenience when a sensible default value is needed. */
+    data object Empty : PackageChangeModel {
+        override val packageName: String
+            get() = ""
+
+        override val packageUid: Int
+            get() = 0
+
+        override val timeMillis: Long
+            get() = 0
+    }
+
+    /**
+     * An existing application package was uninstalled.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to false.
+     */
+    data class Uninstalled(
+        override val packageName: String,
+        override val packageUid: Int,
+        @CurrentTimeMillisLong override val timeMillis: Long = 0,
+    ) : PackageChangeModel
+
+    /**
+     * A new version of an existing application is going to be installed.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to true.
+     */
+    data class UpdateStarted(
+        override val packageName: String,
+        override val packageUid: Int,
+        @CurrentTimeMillisLong override val timeMillis: Long = 0,
+    ) : PackageChangeModel
+
+    /**
+     * A new version of an existing application package has been installed, replacing the old
+     * version.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to true.
+     */
+    data class UpdateFinished(
+        override val packageName: String,
+        override val packageUid: Int,
+        @CurrentTimeMillisLong override val timeMillis: Long = 0,
+    ) : PackageChangeModel
+
+    /**
+     * A new application package has been installed.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to false.
+     */
+    data class Installed(
+        override val packageName: String,
+        override val packageUid: Int,
+        @CurrentTimeMillisLong override val timeMillis: Long = 0,
+    ) : PackageChangeModel
+
+    /**
+     * An existing application package has been changed (for example, a component has been enabled
+     * or disabled).
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_CHANGED] broadcast.
+     */
+    data class Changed(
+        override val packageName: String,
+        override val packageUid: Int,
+        @CurrentTimeMillisLong override val timeMillis: Long = 0,
+    ) : PackageChangeModel
+
+    fun isSamePackage(other: PackageChangeModel) =
+        this.packageName == other.packageName && this.packageUid == other.packageUid
+}
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 779446d..3b727d2 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
@@ -97,7 +97,7 @@
     fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
 
     @Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
-    fun getWidgetByIdNow(id: Int): CommunalWidgetItem
+    fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
 
     @Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem)
 
@@ -120,7 +120,9 @@
     fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
         widgetIdToPriorityMap.forEach { (id, priority) ->
             val widget = getWidgetByIdNow(id)
-            updateItemRank(widget.itemId, priority)
+            if (widget != null) {
+                updateItemRank(widget.itemId, priority)
+            }
         }
     }
 
@@ -134,9 +136,13 @@
     }
 
     @Transaction
-    fun deleteWidgetById(widgetId: Int) {
-        val widget = getWidgetByIdNow(widgetId)
+    fun deleteWidgetById(widgetId: Int): Boolean {
+        val widget =
+            getWidgetByIdNow(widgetId) ?: // no entry to delete from db
+            return false
+
         deleteItemRankById(widget.itemId)
         deleteWidgets(widget)
+        return true
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index cf2e33c..33edb80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -16,15 +16,34 @@
 
 package com.android.systemui.communal.data.model
 
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
 /** Data model of media on the communal hub. */
 data class CommunalMediaModel(
-    val hasAnyMediaOrRecommendation: Boolean,
+    val hasActiveMediaOrRecommendation: Boolean,
     val createdTimestampMillis: Long = 0L,
-) {
+) : Diffable<CommunalMediaModel> {
     companion object {
         val INACTIVE =
             CommunalMediaModel(
-                hasAnyMediaOrRecommendation = false,
+                hasActiveMediaOrRecommendation = false,
             )
     }
+
+    override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
+        if (hasActiveMediaOrRecommendation != prevVal.hasActiveMediaOrRecommendation) {
+            row.logChange(
+                columnName = "isMediaActive",
+                value = hasActiveMediaOrRecommendation,
+            )
+        }
+
+        if (createdTimestampMillis != prevVal.createdTimestampMillis) {
+            row.logChange(
+                columnName = "mediaCreationTimestamp",
+                value = createdTimestampMillis.toString(),
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e8a561b..201be51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -18,6 +18,9 @@
 
 import com.android.systemui.communal.data.model.CommunalMediaModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import javax.inject.Inject
@@ -34,6 +37,7 @@
 @Inject
 constructor(
     private val mediaDataManager: MediaDataManager,
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) : CommunalMediaRepository {
 
     private val mediaDataListener =
@@ -61,13 +65,18 @@
     private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
         MutableStateFlow(CommunalMediaModel.INACTIVE)
 
-    override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+    override val mediaModel: Flow<CommunalMediaModel> =
+        _mediaModel.logDiffsForTable(
+            tableLogBuffer = tableLogBuffer,
+            columnPrefix = "",
+            initialValue = CommunalMediaModel.INACTIVE,
+        )
 
     private fun updateMediaModel(data: MediaData? = null) {
-        if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+        if (mediaDataManager.hasActiveMediaOrRecommendation()) {
             _mediaModel.value =
                 CommunalMediaModel(
-                    hasAnyMediaOrRecommendation = true,
+                    hasActiveMediaOrRecommendation = true,
                     createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
                 )
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index c2ea2e9..0e9b32f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -21,9 +21,15 @@
 import android.content.pm.UserInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -59,11 +65,21 @@
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val userRepository: UserRepository,
     private val userFileManager: UserFileManager,
+    @CommunalLog logBuffer: LogBuffer,
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) : CommunalPrefsRepository {
 
+    private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+
     override val isCtaDismissed: Flow<Boolean> =
         userRepository.selectedUserInfo
             .flatMapLatest(::observeCtaDismissState)
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "",
+                columnName = "isCtaDismissed",
+                initialValue = false,
+            )
             .stateIn(
                 scope = backgroundScope,
                 started = SharingStarted.WhileSubscribed(),
@@ -76,11 +92,13 @@
                 .edit()
                 .putBoolean(CTA_DISMISSED_STATE, true)
                 .apply()
+
+            logger.i("Dismissed CTA tile")
         }
 
     private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
-        userFileManager
-            .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+        getSharedPrefsForUser(user)
+            .observe(CTA_DISMISSED_STATE)
             // Emit at the start of collection to ensure we get an initial value
             .onStart { emit(Unit) }
             .map { getCtaDismissedState() }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 046aaaa..4c06e97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -25,6 +25,9 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -66,6 +69,7 @@
     private val userRepository: UserRepository,
     private val secureSettings: SecureSettings,
     @CommunalLog logBuffer: LogBuffer,
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) : CommunalTutorialRepository {
 
     companion object {
@@ -94,6 +98,12 @@
         settingsState
             .map { it.hubModeTutorialState }
             .filterNotNull()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "",
+                columnName = "tutorialSettingState",
+                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
+            )
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
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 f36547b..2ac9d05 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
@@ -54,8 +54,11 @@
         configurator: WidgetConfigurator? = null
     ) {}
 
-    /** Delete a widget by id from app widget service and the database. */
-    fun deleteWidget(widgetId: Int) {}
+    /** Delete a widget by id from the database. */
+    fun deleteWidgetFromDb(widgetId: Int) {}
+
+    /** Delete a widget by id from app widget host. */
+    fun deleteWidgetFromHost(widgetId: Int) {}
 
     /**
      * Update the order of widgets in the database.
@@ -143,9 +146,18 @@
         }
     }
 
-    override fun deleteWidget(widgetId: Int) {
+    override fun deleteWidgetFromDb(widgetId: Int) {
         bgScope.launch {
-            communalWidgetDao.deleteWidgetById(widgetId)
+            if (communalWidgetDao.deleteWidgetById(widgetId)) {
+                logger.i("Deleted widget with id $widgetId from DB .")
+            } else {
+                logger.w("Widget with id $widgetId cannot be deleted from DB.")
+            }
+        }
+    }
+
+    override fun deleteWidgetFromHost(widgetId: Int) {
+        bgScope.launch {
             appWidgetHost.deleteAppWidgetId(widgetId)
             logger.i("Deleted widget with id $widgetId.")
         }
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 80fee64..950ac3c 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
@@ -33,16 +33,25 @@
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.BooleanFlowOperators.and
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.BooleanFlowOperators.or
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
@@ -50,6 +59,8 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
 
 /** Encapsulates business-logic related to communal mode. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -57,6 +68,7 @@
 class CommunalInteractor
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
     private val communalPrefsRepository: CommunalPrefsRepository,
@@ -65,8 +77,12 @@
     userRepository: UserRepository,
     keyguardInteractor: KeyguardInteractor,
     private val appWidgetHost: CommunalAppWidgetHost,
-    private val editWidgetsActivityStarter: EditWidgetsActivityStarter
+    private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+    @CommunalLog logBuffer: LogBuffer,
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) {
+    private val logger = Logger(logBuffer, "CommunalInteractor")
+
     private val _editModeOpen = MutableStateFlow(false)
 
     /** Whether edit mode is currently open. */
@@ -85,6 +101,22 @@
                 or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
             )
             .distinctUntilChanged()
+            .onEach { available ->
+                logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
+                    bool1 = available
+                }
+            }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "",
+                columnName = "isCommunalAvailable",
+                initialValue = false,
+            )
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                replay = 1,
+            )
 
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
@@ -129,11 +161,36 @@
             .distinctUntilChanged()
 
     /**
-     * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
-     * [CommunalSceneKey.Communal].
+     * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
+     * the [CommunalSceneKey.Communal].
+     *
+     * This will be true as soon as the desired scene is set programmatically or at whatever point
+     * during a fling that SceneTransitionLayout determines that the end state will be the communal
+     * scene. The value also does not change while flinging away until the target scene is no longer
+     * communal.
+     *
+     * If you need a flow that is only true when communal is fully showing and not in transition,
+     * use [isIdleOnCommunal].
      */
+    // TODO(b/323215860): rename to something more appropriate after cleaning up usages
     val isCommunalShowing: Flow<Boolean> =
-        communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+        communalRepository.desiredScene
+            .map { it == CommunalSceneKey.Communal }
+            .distinctUntilChanged()
+            .onEach { showing ->
+                logger.i({ "Communal is ${if (bool1) "showing" else "gone"}" }) { bool1 = showing }
+            }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "",
+                columnName = "isCommunalShowing",
+                initialValue = false,
+            )
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                replay = 1,
+            )
 
     /**
      * Flow that emits a boolean if the communal UI is fully visible and not in transition.
@@ -146,6 +203,16 @@
             it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
         }
 
+    /**
+     * Flow that emits a boolean if any portion of the communal UI is visible at all.
+     *
+     * This flow will be true during any transition and when idle on the communal scene.
+     */
+    val isCommunalVisible: Flow<Boolean> =
+        communalRepository.transitionState.map {
+            !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank)
+        }
+
     /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
     fun onSceneChanged(newScene: CommunalSceneKey) {
         communalRepository.setDesiredScene(newScene)
@@ -170,9 +237,15 @@
         configurator: WidgetConfigurator?,
     ) = widgetRepository.addWidget(componentName, priority, configurator)
 
-    /** Delete a widget by id. */
-    fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
+    /**
+     * Delete a widget by id from the database. [CommunalAppWidgetHostStartable] invokes this
+     * function to manage the deletion from the database for uninstalled or user-deleted widgets,
+     * following the removal of a widget from the host.
+     */
+    fun deleteWidgetFromDb(id: Int) = widgetRepository.deleteWidgetFromDb(id)
 
+    /** Delete a widget by id from AppWidgetHost. Called when user deletes a widget from the hub */
+    fun deleteWidgetFromHost(id: Int) = widgetRepository.deleteWidgetFromHost(id)
     /**
      * Reorder the widgets.
      *
@@ -245,7 +318,7 @@
             )
 
             // Add UMO
-            if (media.hasAnyMediaOrRecommendation) {
+            if (media.hasActiveMediaOrRecommendation) {
                 ongoingContent.add(
                     CommunalContentModel.Umo(
                         createdTimestampMillis = media.createdTimestampMillis,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 309c84e..1404ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -22,6 +22,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,6 +52,7 @@
     keyguardInteractor: KeyguardInteractor,
     private val communalRepository: CommunalRepository,
     communalInteractor: CommunalInteractor,
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) {
     /** An observable for whether the tutorial is available. */
     val isTutorialAvailable: StateFlow<Boolean> =
@@ -61,6 +65,12 @@
                     isKeyguardVisible &&
                     tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
             }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "",
+                columnName = "isTutorialAvailable",
+                initialValue = false,
+            )
             .stateIn(
                 scope = scope,
                 started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index c5dac77..80db535 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,14 +19,12 @@
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
-import android.app.smartspace.SmartspaceTarget
 import android.content.Context
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
@@ -64,11 +62,6 @@
     // A shadow copy of listeners is maintained to track whether the session should remain open.
     private var listeners = mutableSetOf<SmartspaceTargetListener>()
 
-    private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>()
-
-    // Smartspace can be used on multiple displays, such as when the user casts their screen
-    private var smartspaceViews = mutableSetOf<SmartspaceView>()
-
     var preconditionListener =
         object : SmartspacePrecondition.Listener {
             override fun onCriteriaChanged() {
@@ -101,9 +94,7 @@
         }
 
     private fun hasActiveSessionListeners(): Boolean {
-        return smartspaceViews.isNotEmpty() ||
-            listeners.isNotEmpty() ||
-            unfilteredListeners.isNotEmpty()
+        return listeners.isNotEmpty()
     }
 
     private fun connectSession() {
@@ -188,8 +179,4 @@
     private fun reloadSmartspace() {
         session?.requestSmartspaceUpdate()
     }
-
-    private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) {
-        unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
-    }
 }
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 ebcfb8b..69d5581 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
@@ -21,6 +21,9 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
 import javax.inject.Inject
@@ -29,6 +32,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 /** The view model for communal hub in edit mode. */
 @SysUISingleton
@@ -38,21 +42,27 @@
     private val communalInteractor: CommunalInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     private val uiEventLogger: UiEventLogger,
+    @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+    private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
+
     override val isEditMode = true
 
     // Only widgets are editable. The CTA tile comes last in the list and remains visible.
     override val communalContent: Flow<List<CommunalContentModel>> =
-        communalInteractor.widgetContent.map { widgets ->
-            widgets + listOf(CommunalContentModel.CtaTileInEditMode())
-        }
+        communalInteractor.widgetContent
+            .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+            .onEach { models ->
+                logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+            }
 
     private val _reorderingWidgets = MutableStateFlow(false)
 
     override val reorderingWidgets: StateFlow<Boolean>
         get() = _reorderingWidgets
 
-    override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+    override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidgetFromHost(id)
 
     override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7a3705..40d2d16 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,9 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 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.log.dagger.CommunalLog
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.controls.ui.MediaHostState
@@ -37,6 +40,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 /** The default view model used for showing the communal hub. */
@@ -48,22 +52,30 @@
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+    @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+    private val logger = Logger(logBuffer, "CommunalViewModel")
+
     @OptIn(ExperimentalCoroutinesApi::class)
     override val communalContent: Flow<List<CommunalContentModel>> =
-        tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
-            if (isTutorialMode) {
-                return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+        tutorialInteractor.isTutorialAvailable
+            .flatMapLatest { isTutorialMode ->
+                if (isTutorialMode) {
+                    return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+                }
+                combine(
+                    communalInteractor.ongoingContent,
+                    communalInteractor.widgetContent,
+                    communalInteractor.ctaTileContent,
+                ) { ongoing, widgets, ctaTile,
+                    ->
+                    ongoing + widgets + ctaTile
+                }
             }
-            combine(
-                communalInteractor.ongoingContent,
-                communalInteractor.widgetContent,
-                communalInteractor.ctaTileContent,
-            ) { ongoing, widgets, ctaTile,
-                ->
-                ongoing + widgets + ctaTile
+            .onEach { models ->
+                logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
             }
-        }
 
     private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
     override val isPopupOnDismissCtaShowing: Flow<Boolean> =
@@ -75,7 +87,7 @@
         with(mediaHost) {
             expansion = MediaHostState.EXPANDED
             expandedMatchesParentHeight = true
-            showsOnlyActiveMedia = false
+            showsOnlyActiveMedia = true
             falsingProtectionNeeded = false
             init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index fb9abeb..6fd0fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -50,7 +50,7 @@
             .launchIn(bgScope)
 
         appWidgetHost.appWidgetIdToRemove
-            .onEach { appWidgetId -> communalInteractor.deleteWidget(appWidgetId) }
+            .onEach { appWidgetId -> communalInteractor.deleteWidgetFromDb(appWidgetId) }
             .launchIn(bgScope)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index eb6dc43..92e8153 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,6 +32,9 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.res.R
 import javax.inject.Inject
 
@@ -42,7 +45,8 @@
     private val communalViewModel: CommunalEditModeViewModel,
     private var windowManagerService: IWindowManager? = null,
     private val uiEventLogger: UiEventLogger,
-    private val widgetConfiguratorFactory: WidgetConfigurationController.Factory
+    private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+    @CommunalLog logBuffer: LogBuffer,
 ) : ComponentActivity() {
     companion object {
         private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
@@ -54,6 +58,8 @@
         const val EXTRA_PRESELECTED_KEY = "preselected_key"
     }
 
+    private val logger = Logger(logBuffer, "EditWidgetsActivity")
+
     private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
 
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
@@ -157,11 +163,15 @@
 
     override fun onStart() {
         super.onStart()
+
+        logger.i("Starting the communal widget editor activity")
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
     }
 
     override fun onStop() {
         super.onStop()
+
+        logger.i("Stopping the communal widget editor activity")
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 947cb02..9a4dfdd 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.compose
 
-import android.app.Dialog
 import android.content.Context
 import android.view.View
 import android.view.WindowInsets
@@ -28,12 +27,13 @@
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.StateFlow
@@ -96,11 +96,11 @@
         sceneByKey: Map<SceneKey, Scene>,
     ): View
 
-    /** Creates sticky key dialog presenting provided [viewModel] */
-    fun createStickyKeysDialog(
-        dialogFactory: SystemUIDialogFactory,
+    /** Creates sticky key indicator content presenting provided [viewModel] */
+    fun createStickyKeysIndicatorContent(
+        context: Context,
         viewModel: StickyKeysIndicatorViewModel
-    ): Dialog
+    ): View
 
     /** Create a [View] to represent [viewModel] on screen. */
     fun createCommunalView(
@@ -117,4 +117,11 @@
 
     /** Creates a container that hosts the communal UI and handles gesture transitions. */
     fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
+
+    /** Creates a [View] that represents the Lockscreen. */
+    fun createLockscreen(
+        context: Context,
+        viewModel: LockscreenContentViewModel,
+        blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index ae9c37a..b35bec4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -17,11 +17,16 @@
 
 package com.android.systemui.controls.panels
 
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+
 /**
  * Repository for keeping track of which packages the panel has authorized to show control panels
  * (embedded activity).
  */
 interface AuthorizedPanelsRepository {
+    /** Exposes the authorized panels as a [Flow] for subscribing to updates */
+    fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>>
 
     /** A set of package names that the user has previously authorized to show panels. */
     fun getAuthorizedPanels(): Set<String>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 4e935df..7c2dae3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -19,11 +19,16 @@
 
 import android.content.Context
 import android.content.SharedPreferences
+import android.os.UserHandle
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 class AuthorizedPanelsRepositoryImpl
 @Inject
@@ -33,19 +38,24 @@
     private val userTracker: UserTracker,
 ) : AuthorizedPanelsRepository {
 
+    override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
+        val prefs = instantiateSharedPrefs(user)
+        return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+    }
+
     override fun getAuthorizedPanels(): Set<String> {
-        return getAuthorizedPanelsInternal(instantiateSharedPrefs())
+        return getAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle))
     }
 
     override fun getPreferredPackages(): Set<String> =
         context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
 
     override fun addAuthorizedPanels(packageNames: Set<String>) {
-        addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
+        addAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle), packageNames)
     }
 
     override fun removeAuthorizedPanels(packageNames: Set<String>) {
-        with(instantiateSharedPrefs()) {
+        with(instantiateSharedPrefs(userTracker.userHandle)) {
             val currentSet = getAuthorizedPanelsInternal(this)
             edit().putStringSet(KEY, currentSet - packageNames).apply()
         }
@@ -63,12 +73,12 @@
         sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
     }
 
-    private fun instantiateSharedPrefs(): SharedPreferences {
+    private fun instantiateSharedPrefs(user: UserHandle): SharedPreferences {
         val sharedPref =
             userFileManager.getSharedPreferences(
                 DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
                 Context.MODE_PRIVATE,
-                userTracker.userId,
+                user.identifier,
             )
 
         // We should add default packages when we've never run this
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 0baa81a..9be04940 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -20,21 +20,18 @@
 import android.content.Context
 import android.content.SharedPreferences
 import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -43,9 +40,7 @@
 constructor(
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
-    private val featureFlags: FeatureFlags,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application private val applicationScope: CoroutineScope
+    @Background private val bgDispatcher: CoroutineDispatcher
 ) : SelectedComponentRepository {
 
     private companion object {
@@ -66,22 +61,11 @@
     override fun selectedComponentFlow(
         userHandle: UserHandle
     ): Flow<SelectedComponentRepository.SelectedComponent?> {
-        return conflatedCallbackFlow {
-                val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
-                val listener =
-                    SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
-                        applicationScope.launch(bgDispatcher) {
-                            if (key == PREF_COMPONENT) {
-                                trySend(getSelectedComponent(userHandle))
-                            }
-                        }
-                    }
-                sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
-                send(getSelectedComponent(userHandle))
-                awaitClose {
-                    sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
-                }
-            }
+        val prefs = getSharedPreferencesForUser(userHandle.identifier)
+        return prefs
+            .observe(PREF_COMPONENT)
+            .onStart { emit(Unit) }
+            .map { getSelectedComponent(userHandle) }
             .flowOn(bgDispatcher)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 20bfbc9..3458d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -26,8 +26,8 @@
 import androidx.annotation.WorkerThread
 import com.android.systemui.CoreStartable
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.data.shared.model.PackageChangeModel
-import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.domain.interactor.PackageChangeInteractor
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -70,7 +71,7 @@
     private val userTracker: UserTracker,
     private val authorizedPanelsRepository: AuthorizedPanelsRepository,
     private val selectedComponentRepository: SelectedComponentRepository,
-    private val packageChangeRepository: PackageChangeRepository,
+    private val packageChangeInteractor: PackageChangeInteractor,
     private val userManager: UserManager,
     private val broadcastDispatcher: BroadcastDispatcher,
 ) : CoreStartable {
@@ -114,12 +115,13 @@
 
     private fun monitorPackageUninstall() {
         packageJob?.cancel()
-        packageJob = packageChangeRepository.packageChanged(userTracker.userHandle)
+        packageJob = packageChangeInteractor.packageChanged(userTracker.userHandle)
+            .filterIsInstance<PackageChangeModel.Uninstalled>()
             .filter {
                 val selectedPackage =
                     selectedComponentRepository.getSelectedComponent()?.componentName?.packageName
                 // Selected package was uninstalled
-                (it is PackageChangeModel.Uninstalled) && (it.packageName == selectedPackage)
+                it.packageName == selectedPackage
             }
             .onEach { selectedComponentRepository.removeSelectedComponent() }
             .flowOn(bgDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 92eeace..904d5898 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -23,6 +23,7 @@
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
 import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.recordissue.IssueRecordingService;
 import com.android.systemui.screenrecord.RecordingService;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 import com.android.systemui.wallpapers.ImageWallpaper;
@@ -85,4 +86,11 @@
     @IntoMap
     @ClassKey(RecordingService.class)
     public abstract Service bindRecordingService(RecordingService service);
+
+    /** Inject into IssueRecordingService */
+    @Binds
+    @IntoMap
+    @ClassKey(IssueRecordingService.class)
+    public abstract Service bindIssueRecordingService(IssueRecordingService service);
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9d1e94..dd186d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -42,7 +42,7 @@
 import com.android.systemui.rotationlock.RotationLockModule;
 import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.settings.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.ShadeModule;
 import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5ee2045..a3d6ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,7 +47,7 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
 import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.settings.dagger.MultiUserUtilsModule
+import com.android.systemui.settings.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.ImmersiveModeConfirmation
 import com.android.systemui.statusbar.gesture.GesturePointerEventListener
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index efcbd47..28fd9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -27,6 +27,7 @@
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CameraProtectionModule;
 import com.android.systemui.accessibility.AccessibilityModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.appops.dagger.AppOpsModule;
@@ -177,6 +178,7 @@
         BouncerInteractorModule.class,
         BouncerRepositoryModule.class,
         BouncerViewModule.class,
+        CameraProtectionModule.class,
         ClipboardOverlayModule.class,
         ClockRegistryModule.class,
         CommunalModule.class,
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 c93b8e1..1230156 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
@@ -292,7 +292,7 @@
 
     private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
         return if (DEBUG) {
-            traceEach(flowName, logcat = true)
+            traceEach(flowName, logcat = true, traceEmissionCount = true)
         } else {
             this
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index c331164..537cacd 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -93,7 +93,11 @@
             }
 
             mDockState = dockState;
-            if (isPulsing()) {
+            if (mMachine.isExecutingTransition() || isPulsing()) {
+                // If the device is in the middle of executing a transition or is pulsing,
+                // exit early instead of requesting a new state. DozeMachine
+                // will check the docked state and resolveIntermediateState in the next
+                // transition after pulse done.
                 return;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index e04a505..e74814a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -21,11 +21,20 @@
 import android.service.dreams.DreamService
 import android.window.TaskFragmentInfo
 import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dreams.DreamLogger
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.DreamLog
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 
 class HomeControlsDreamService
 @Inject
@@ -34,11 +43,13 @@
     private val taskFragmentFactory: TaskFragmentComponent.Factory,
     private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
     private val dreamActivityProvider: DreamActivityProvider,
+    @Background private val bgDispatcher: CoroutineDispatcher,
     @DreamLog logBuffer: LogBuffer
 ) : DreamService() {
-    private lateinit var taskFragmentComponent: TaskFragmentComponent
-
+    private val serviceJob = SupervisorJob()
+    private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
     private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+    private lateinit var taskFragmentComponent: TaskFragmentComponent
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
@@ -47,6 +58,11 @@
             finish()
             return
         }
+
+        // Start monitoring package updates to possibly restart the dream if the home controls
+        // package is updated while we are dreaming.
+        serviceScope.launch { homeControlsComponentInteractor.monitorUpdatesAndRestart() }
+
         taskFragmentComponent =
             taskFragmentFactory
                 .create(
@@ -62,6 +78,7 @@
         if (taskFragmentInfo.isEmpty) {
             logger.d("Finishing dream due to TaskFragment being empty")
             finish()
+            homeControlsComponentInteractor.onTaskFragmentEmpty()
         }
     }
 
@@ -84,5 +101,19 @@
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
         taskFragmentComponent.destroy()
+        serviceScope.launch {
+            delay(CANCELLATION_DELAY_AFTER_DETACHED)
+            serviceJob.cancel("Dream detached from window")
+        }
+    }
+
+    private companion object {
+        /**
+         * Defines how long after the dream ends that we should keep monitoring for package updates
+         * to attempt a restart of the dream. This should be larger than
+         * [MAX_UPDATE_CORRELATION_DELAY] as it also includes the time the package update takes to
+         * complete.
+         */
+        val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 91e0547..f0067dc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -16,8 +16,13 @@
 
 package com.android.systemui.dreams.homecontrols.domain.interactor
 
+import android.annotation.SuppressLint
+import android.app.DreamManager
 import android.content.ComponentName
+import android.os.UserHandle
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.domain.interactor.PackageChangeInteractor
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
@@ -27,15 +32,24 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.kotlin.pairwiseBy
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
@@ -47,24 +61,33 @@
 @Inject
 constructor(
     private val selectedComponentRepository: SelectedComponentRepository,
-    private val controlsComponent: ControlsComponent,
-    private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+    controlsComponent: ControlsComponent,
+    authorizedPanelsRepository: AuthorizedPanelsRepository,
     userRepository: UserRepository,
+    private val packageChangeInteractor: PackageChangeInteractor,
+    private val systemClock: SystemClock,
+    private val dreamManager: DreamManager,
     @Background private val bgScope: CoroutineScope
 ) {
-    private val controlsListingController =
+    private val controlsListingController: ControlsListingController? =
         controlsComponent.getControlsListingController().getOrNull()
 
     /** Gets the current user's selected panel, or null if there isn't one */
-    private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+    private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
         userRepository.selectedUserInfo
             .flatMapLatest { user ->
                 selectedComponentRepository.selectedComponentFlow(user.userHandle)
             }
             .map { if (it?.isPanel == true) it else null }
 
-    /** Gets all the available panels which are authorized by the user */
-    private fun allPanelItem(): Flow<List<PanelComponent>> {
+    /** Gets the current user's authorized panels */
+    private val allAuthorizedPanels: Flow<Set<String>> =
+        userRepository.selectedUserInfo.flatMapLatest { user ->
+            authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
+        }
+
+    /** Gets all the available services from [ControlsListingController] */
+    private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
         if (controlsListingController == null) {
             return emptyFlow()
         }
@@ -79,26 +102,92 @@
                 awaitClose { controlsListingController.removeCallback(listener) }
             }
             .onStart { emit(controlsListingController.getCurrentServices()) }
-            .map { serviceInfos ->
-                val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
-                serviceInfos.mapNotNull {
-                    val panelActivity = it.panelActivity
-                    if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
-                        PanelComponent(it.componentName, panelActivity)
-                    } else {
-                        null
-                    }
+    }
+
+    /** Gets all panels which are available and authorized by the user */
+    private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
+        combine(
+            allAvailableServices(),
+            allAuthorizedPanels,
+        ) { serviceInfos, authorizedPanels ->
+            serviceInfos.mapNotNull {
+                val panelActivity = it.panelActivity
+                if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+                    PanelComponent(it.componentName, panelActivity)
+                } else {
+                    null
                 }
             }
-    }
+        }
+
     val panelComponent: StateFlow<ComponentName?> =
-        combine(allPanelItem(), selectedItem) { items, selected ->
+        combine(
+                allAvailableAndAuthorizedPanels,
+                selectedPanel,
+            ) { panels, selected ->
                 val item =
-                    items.firstOrNull { it.componentName == selected?.componentName }
-                        ?: items.firstOrNull()
+                    panels.firstOrNull { it.componentName == selected?.componentName }
+                        ?: panels.firstOrNull()
                 item?.panelActivity
             }
             .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
 
-    data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+    private val taskFragmentFinished =
+        MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+    fun onTaskFragmentEmpty() {
+        taskFragmentFinished.tryEmit(systemClock.currentTimeMillis())
+    }
+
+    /**
+     * Monitors if the current home panel package is updated and causes the dream to finish, and
+     * attempts to restart the dream in this case.
+     */
+    @SuppressLint("MissingPermission")
+    suspend fun monitorUpdatesAndRestart() {
+        taskFragmentFinished.resetReplayCache()
+        panelComponent
+            .flatMapLatest { component ->
+                if (component == null) return@flatMapLatest emptyFlow()
+                packageChangeInteractor.packageChanged(UserHandle.CURRENT, component.packageName)
+            }
+            .filter { it.isUpdate() }
+            // Wait for an UpdatedStarted - UpdateFinished pair to ensure the update has finished.
+            .pairwiseBy(::validateUpdatePair)
+            .filterNotNull()
+            .sample(taskFragmentFinished, ::Pair)
+            .filter { (updateStarted, lastFinishedTimestamp) ->
+                abs(updateStarted.timeMillis - lastFinishedTimestamp) <=
+                    MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds
+            }
+            .collect { dreamManager.startDream() }
+    }
+
+    private data class PanelComponent(
+        val componentName: ComponentName,
+        val panelActivity: ComponentName,
+    )
+
+    companion object {
+        /**
+         * The maximum delay between a package update **starting** and the task fragment finishing
+         * which causes us to correlate the package update as the cause of the task fragment
+         * finishing.
+         */
+        val MAX_UPDATE_CORRELATION_DELAY = 500.milliseconds
+    }
 }
+
+private fun PackageChangeModel.isUpdate() =
+    this is PackageChangeModel.UpdateStarted || this is PackageChangeModel.UpdateFinished
+
+private fun validateUpdatePair(
+    updateStarted: PackageChangeModel,
+    updateFinished: PackageChangeModel
+): PackageChangeModel.UpdateStarted? =
+    when {
+        !updateStarted.isSamePackage(updateFinished) -> null
+        updateStarted !is PackageChangeModel.UpdateStarted -> null
+        updateFinished !is PackageChangeModel.UpdateFinished -> null
+        else -> updateStarted
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index c9b56a2..05279fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -23,7 +23,6 @@
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
-import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import java.util.Optional;
@@ -34,17 +33,14 @@
 /** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
 public class CommunalTouchHandler implements DreamTouchHandler {
     private final int mInitiationWidth;
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final Optional<CentralSurfaces> mCentralSurfaces;
 
     @Inject
     public CommunalTouchHandler(
             Optional<CentralSurfaces> centralSurfaces,
-            NotificationShadeWindowController notificationShadeWindowController,
             @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
         mInitiationWidth = initiationWidth;
         mCentralSurfaces = centralSurfaces;
-        mNotificationShadeWindowController = notificationShadeWindowController;
     }
 
     @Override
@@ -60,9 +56,8 @@
     }
 
     private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
-        // Force the notification shade window open (otherwise the hub won't show while swiping).
-        mNotificationShadeWindowController.setForcePluginOpen(true, this);
-
+        // Notification shade window has its own logic to be visible if the hub is open, no need to
+        // do anything here other than send touch events over.
         session.registerInputListener(ev -> {
             surfaces.handleDreamTouch((MotionEvent) ev);
             if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index df0566e..41ce3fd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,9 +23,12 @@
 import com.android.server.notification.Flags.politeNotifications
 import com.android.server.notification.Flags.vibrateWhileUnlocked
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -55,6 +58,11 @@
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
         SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+
+        // ComposeLockscreen dependencies
+        ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token
+        ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
+        ComposeLockscreen.token dependsOn migrateClocksToBlueprint
     }
 
     private inline val politeNotifications
@@ -65,4 +73,6 @@
         get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
     private inline val keyguardBottomAreaRefactor
         get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+    private inline val migrateClocksToBlueprint
+        get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c69c9ef..6eff792 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@
     @JvmField
     val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
 
-    // TODO(b/287205379): Tracking bug
-    @JvmField
-    val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
-
     /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
     @JvmField
     val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index ec29bd6..89cdd25 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -32,19 +32,13 @@
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
 import javax.inject.Inject
 
 interface StickyKeysRepository {
@@ -53,14 +47,12 @@
 }
 
 @SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
 class StickyKeysRepositoryImpl
 @Inject
 constructor(
     private val inputManager: InputManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    private val secureSettings: SecureSettings,
-    userRepository: UserRepository,
+    secureSettingsRepository: UserAwareSecureSettingsRepository,
     private val stickyKeysLogger: StickyKeysLogger,
 ) : StickyKeysRepository {
 
@@ -78,25 +70,10 @@
             .flowOn(backgroundDispatcher)
 
     override val settingEnabled: Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { stickyKeySettingObserver(it.id) }
-            .flowOn(backgroundDispatcher)
-
-    private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
-        return secureSettings
-            .observerFlow(userId, SETTING_KEY)
-            .onStart { emit(Unit) }
-            .map { isSettingEnabledForCurrentUser(userId) }
-            .distinctUntilChanged()
+        secureSettingsRepository
+            .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
             .onEach { stickyKeysLogger.logNewSettingValue(it) }
-    }
-
-    private fun isSettingEnabledForCurrentUser(userId: Int) =
-        secureSettings.getIntForUser(
-            /* name= */ SETTING_KEY,
-            /* default= */ 0,
-            /* userHandle= */ userId
-        ) != 0
+            .flowOn(backgroundDispatcher)
 
     private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
         val keys = linkedMapOf<ModifierKey, Locked>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index d5f082a..72a81cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -19,10 +19,10 @@
 @JvmInline
 value class Locked(val locked: Boolean)
 
-enum class ModifierKey(val text: String) {
+enum class ModifierKey(val displayedText: String) {
     ALT("ALT LEFT"),
     ALT_GR("ALT RIGHT"),
     CTRL("CTRL"),
-    META("META"),
+    META("ACTION"),
     SHIFT("SHIFT"),
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
new file mode 100644
index 0000000..3ed58a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
@@ -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 com.android.systemui.keyboard.stickykeys.ui
+
+import android.app.Dialog
+import android.content.Context
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import androidx.activity.ComponentDialog
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StickyKeyDialogFactory
+@Inject
+constructor(
+    @Application val context: Context,
+) {
+
+    fun create(viewModel: StickyKeysIndicatorViewModel): Dialog {
+        return createStickyKeyIndicator(viewModel)
+    }
+
+    private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog {
+        return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply {
+            // because we're requesting window feature it must be called before setting content
+            window?.setStickyKeyWindowAttributes()
+            setContentView(ComposeFacade.createStickyKeysIndicatorContent(context, viewModel))
+        }
+    }
+
+    private fun Window.setStickyKeyWindowAttributes() {
+        requestFeature(Window.FEATURE_NO_TITLE)
+        setType(TYPE_STATUS_BAR_SUB_PANEL)
+        addFlags(FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCHABLE)
+        clearFlags(FLAG_DIM_BEHIND)
+        setGravity(Gravity.TOP or Gravity.END)
+        attributes =
+            WindowManager.LayoutParams().apply {
+                copyFrom(attributes)
+                title = "StickyKeysIndicator"
+            }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
index c3a618d..842fd04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -18,16 +18,11 @@
 
 import android.app.Dialog
 import android.util.Log
-import android.view.Gravity
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.Window
-import android.view.WindowManager
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import javax.inject.Inject
@@ -37,7 +32,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val dialogFactory: SystemUIDialogFactory,
+    private val stickyKeyDialogFactory: StickyKeyDialogFactory,
     private val viewModel: StickyKeysIndicatorViewModel,
     private val stickyKeysLogger: StickyKeysLogger,
 ) {
@@ -57,25 +52,10 @@
                     dialog?.dismiss()
                     dialog = null
                 } else if (dialog == null) {
-                    dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
-                        window?.setAttributes()
-                        show()
-                    }
+                    dialog = stickyKeyDialogFactory.create(viewModel)
+                    dialog?.show()
                 }
             }
         }
     }
-
-    private fun Window.setAttributes() {
-        setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-        addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
-        addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
-        clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
-        setGravity(Gravity.TOP or Gravity.END)
-        attributes = WindowManager.LayoutParams().apply {
-            copyFrom(attributes)
-            width = WRAP_CONTENT
-            title = "StickyKeysIndicator"
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 9b83b75..ee3706a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -80,7 +80,7 @@
     }
 
     /**
-     * Click listener for messsage.
+     * Click listener for message.
      */
     public @Nullable View.OnClickListener getClickListener() {
         return mOnClickListener;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b5f9c69..4cabd70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -61,7 +60,6 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
@@ -108,20 +106,7 @@
     private final ScreenOnCoordinator mScreenOnCoordinator;
     private final ShellTransitions mShellTransitions;
     private final DisplayTracker mDisplayTracker;
-    private PowerInteractor mPowerInteractor;
-
-    private static int newModeToLegacyMode(int newMode) {
-        switch (newMode) {
-            case WindowManager.TRANSIT_OPEN:
-            case WindowManager.TRANSIT_TO_FRONT:
-                return MODE_OPENING;
-            case WindowManager.TRANSIT_CLOSE:
-            case WindowManager.TRANSIT_TO_BACK:
-                return MODE_CLOSING;
-            default:
-                return 2; // MODE_CHANGING
-        }
-    }
+    private final PowerInteractor mPowerInteractor;
 
     private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -253,8 +238,7 @@
 
             public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
                     SurfaceControl.Transaction candidateT, IBinder currentTransition,
-                    IRemoteTransitionFinishedCallback candidateFinishCallback)
-                    throws RemoteException {
+                    IRemoteTransitionFinishedCallback candidateFinishCallback) {
                 if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
                     keyguardViewMediator.setPendingLock(true);
                     keyguardViewMediator.cancelKeyguardExitAnimation();
@@ -265,13 +249,13 @@
                     runner.onAnimationCancelled();
                     finish(currentTransition);
                 } catch (RemoteException e) {
-                    // nothing, we'll just let it finish on its own I guess.
+                    // Ignore.
                 }
             }
 
             @Override
-            public void onTransitionConsumed(IBinder transition, boolean aborted)
-                    throws RemoteException {
+            public void onTransitionConsumed(IBinder transition, boolean aborted) {
+                // No-op.
             }
 
             private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@@ -283,7 +267,7 @@
             }
 
             private void finish(IBinder transition) throws RemoteException {
-                IRemoteTransitionFinishedCallback finishCallback = null;
+                final IRemoteTransitionFinishedCallback finishCallback;
                 SurfaceControl.Transaction finishTransaction = null;
 
                 synchronized (mLeashMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 53c81e5..e18e463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -109,7 +109,7 @@
  * This animation will take place entirely within the Launcher window. We can safely unlock the
  * device, end remote animations, etc. even if this is still running.
  */
-const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 633L
+const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 1633L
 
 /**
  * How long to wait for the shade to get out of the way before starting the canned unlock animation.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 7f43fac..abe49ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,12 @@
 import android.content.Context
 import android.view.LayoutInflater
 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 com.android.internal.jank.InteractionJankMonitor
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.KeyguardStatusViewController
@@ -29,10 +35,13 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.shared.ComposeLockscreen
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
@@ -44,6 +53,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
@@ -88,6 +98,8 @@
     private val falsingManager: FalsingManager,
     private val aodAlphaViewModel: AodAlphaViewModel,
     private val keyguardClockViewModel: KeyguardClockViewModel,
+    private val lockscreenContentViewModel: LockscreenContentViewModel,
+    private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -115,11 +127,28 @@
         initializeViews()
 
         if (!SceneContainerFlag.isEnabled) {
-            KeyguardBlueprintViewBinder.bind(
-                keyguardRootView,
-                keyguardBlueprintViewModel,
-                keyguardClockViewModel
-            )
+            if (ComposeLockscreen.isEnabled) {
+                val composeView =
+                    ComposeFacade.createLockscreen(
+                        context = context,
+                        viewModel = lockscreenContentViewModel,
+                        blueprints = lockscreenSceneBlueprintsLazy.get(),
+                    )
+                composeView.id = View.generateViewId()
+                val cs = ConstraintSet()
+                cs.clone(keyguardRootView)
+                cs.connect(composeView.id, START, PARENT_ID, START)
+                cs.connect(composeView.id, END, PARENT_ID, END)
+                cs.connect(composeView.id, TOP, PARENT_ID, TOP)
+                cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
+                keyguardRootView.addView(composeView)
+            } else {
+                KeyguardBlueprintViewBinder.bind(
+                    keyguardRootView,
+                    keyguardBlueprintViewModel,
+                    keyguardClockViewModel,
+                )
+            }
         }
         keyguardBlueprintCommandListener.start()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 794befa..8f08efa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,12 +45,12 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
 import android.app.AlarmManager;
 import android.app.BroadcastOptions;
 import android.app.IActivityTaskManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
-import android.app.WallpaperManager;
 import android.app.WindowConfiguration;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
@@ -174,6 +174,8 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -183,7 +185,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
-import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -326,7 +327,6 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
-    private WallpaperManager mWallpaperManager;
     private final IStatusBarService mStatusBarService;
     private final IBinder mStatusBarDisableToken = new Binder();
     private final UserTracker mUserTracker;
@@ -356,13 +356,13 @@
     private final SecureSettings mSecureSettings;
     private final SystemSettings mSystemSettings;
     private final SystemClock mSystemClock;
-    private SystemPropertiesHelper mSystemPropertiesHelper;
+    private final SystemPropertiesHelper mSystemPropertiesHelper;
 
     /**
      * Used to keep the device awake while to ensure the keyguard finishes opening before
      * we sleep.
      */
-    private PowerManager.WakeLock mShowKeyguardWakeLock;
+    private final PowerManager.WakeLock mShowKeyguardWakeLock;
 
     private final Lazy<KeyguardViewController> mKeyguardViewControllerLazy;
 
@@ -405,13 +405,13 @@
     private boolean mWakeAndUnlocking = false;
 
     /**
-     * Helps remember whether the screen has turned on since the last time
-     * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+     * Helps remember whether the screen has turned on since the last time it turned off due to
+     * timeout. See {@link #onScreenTurnedOff}
      */
     private int mDelayedShowingSequence;
 
     /**
-     * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+     * Similar to {@link #mDelayedShowingSequence}, but it is for profile case.
      */
     private int mDelayedProfileShowingSequence;
 
@@ -439,12 +439,12 @@
     private boolean mGoingToSleep;
 
     // last known state of the cellular connection
-    private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+    private final String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
 
     /**
      * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
      * called.
-     * */
+     */
     private boolean mHiding;
 
     /**
@@ -689,17 +689,18 @@
                             } else {
                                 resetStateLocked();
                             }
-                        }
-                        if (simState == TelephonyManager.SIM_STATE_ABSENT) {
-                            // MVNO SIMs can become transiently NOT_READY when switching networks,
-                            // so we should only lock when they are ABSENT.
-                            if (lastSimStateWasLocked) {
-                                if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
-                                        + "previous state was locked. Reset the state.");
+                        } else {
+                            if (lastSimStateWasLocked && mShowing) {
+                                if (DEBUG_SIM_STATES) {
+                                    Log.d(TAG, "SIM moved to "
+                                            + "NOT_READY/ABSENT/UNKNOWN when the previous state "
+                                            + "was locked. Reset the state.");
+                                }
                                 resetStateLocked();
                             }
-                            mSimWasLocked.append(slotId, false);
                         }
+
+                        mSimWasLocked.append(slotId, false);
                     }
                     break;
                 case TelephonyManager.SIM_STATE_PIN_REQUIRED:
@@ -1088,8 +1089,7 @@
                 public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
                         RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-                    if (!handleOnAnimationStart(
-                                transit, apps, wallpapers, nonApps, finishedCallback)) {
+                    if (!handleOnAnimationStart(apps, finishedCallback)) {
                         // Usually we rely on animation completion to synchronize occluded status,
                         // but there was no animation to play, so just update it now.
                         setOccluded(true /* isOccluded */, false /* animate */);
@@ -1097,9 +1097,8 @@
                     }
                 }
 
-                private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                        RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                        IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                private boolean handleOnAnimationStart(RemoteAnimationTarget[] apps,
+                        IRemoteAnimationFinishedCallback finishedCallback) {
                     if (apps == null || apps.length == 0 || apps[0] == null) {
                         Log.d(TAG, "No apps provided to the OccludeByDream runner; "
                                 + "skipping occluding animation.");
@@ -1107,8 +1106,8 @@
                     }
 
                     final RemoteAnimationTarget primary = apps[0];
-                    final boolean isDream = (apps[0].taskInfo != null
-                            && apps[0].taskInfo.topActivityType
+                    final boolean isDream = (primary.taskInfo != null
+                            && primary.taskInfo.topActivityType
                             == WindowConfiguration.ACTIVITY_TYPE_DREAM);
                     if (!isDream) {
                         Log.w(TAG, "The occluding app isn't Dream; "
@@ -1322,9 +1321,9 @@
     }
 
     private DeviceConfigProxy mDeviceConfig;
-    private DozeParameters mDozeParameters;
-    private SelectedUserInteractor mSelectedUserInteractor;
-    private KeyguardInteractor mKeyguardInteractor;
+    private final DozeParameters mDozeParameters;
+    private final SelectedUserInteractor mSelectedUserInteractor;
+    private final KeyguardInteractor mKeyguardInteractor;
     @VisibleForTesting
     protected FoldGracePeriodProvider mFoldGracePeriodProvider =
             new FoldGracePeriodProvider();
@@ -1346,14 +1345,12 @@
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private boolean mWallpaperSupportsAmbientMode;
-    private ScreenOnCoordinator mScreenOnCoordinator;
     private final KeyguardTransitions mKeyguardTransitions;
 
-    private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
-    private Lazy<ScrimController> mScrimControllerLazy;
-    private IActivityTaskManager mActivityTaskManagerService;
+    private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+    private final Lazy<ScrimController> mScrimControllerLazy;
+    private final IActivityTaskManager mActivityTaskManagerService;
 
-    private FeatureFlags mFeatureFlags;
     private final UiEventLogger mUiEventLogger;
     private final SessionTracker mSessionTracker;
     private final CoroutineDispatcher mMainDispatcher;
@@ -1361,7 +1358,7 @@
             mDreamingToLockscreenTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
-    private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+    private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
@@ -1433,7 +1430,6 @@
         mShadeController = shadeControllerLazy;
         dumpManager.registerDumpable(this);
         mDeviceConfig = deviceConfig;
-        mScreenOnCoordinator = screenOnCoordinator;
         mKeyguardTransitions = keyguardTransitions;
         mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -1445,9 +1441,8 @@
                 mHandler::post,
                 mOnPropertiesChangedListener);
         mInGestureNavigationMode =
-                QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
-                    mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
-                }));
+                QuickStepContract.isGesturalMode(navigationModeController.addListener(mode ->
+                        mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode)));
         mDozeParameters = dozeParameters;
         mSelectedUserInteractor = selectedUserInteractor;
         mKeyguardInteractor = keyguardInteractor;
@@ -1474,7 +1469,6 @@
 
         mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
 
-        mFeatureFlags = featureFlags;
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
@@ -1578,14 +1572,6 @@
                 this::setWallpaperSupportsAmbientMode);
     }
 
-    // TODO(b/273443374) remove, temporary util to get a feature flag
-    private WallpaperManager getWallpaperManager() {
-        if (mWallpaperManager == null) {
-            mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
-        }
-        return mWallpaperManager;
-    }
-
     @Override
     public void start() {
         synchronized (this) {
@@ -1611,11 +1597,11 @@
 
             ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
             if (viewRootImpl != null) {
-                collectFlow(viewRootImpl.getView(),
-                        mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+                DreamingToLockscreenTransitionViewModel viewModel =
+                        mDreamingToLockscreenTransitionViewModel.get();
+                collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
                         getRemoteSurfaceAlphaApplier(), mMainDispatcher);
-                collectFlow(viewRootImpl.getView(),
-                        mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+                collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
                         getFinishedCallbackConsumer(), mMainDispatcher);
             }
         }
@@ -2304,6 +2290,7 @@
         showKeyguard(options);
     }
 
+    @SuppressLint("MissingPermission")
     private void lockProfile(int userId) {
         mTrustManager.setDeviceLockedForUser(userId, true);
     }
@@ -2497,13 +2484,12 @@
     };
 
     /**
-     * This handler will be associated with the policy thread, which will also
-     * be the UI thread of the keyguard.  Since the apis of the policy, and therefore
-     * this class, can be called by other threads, any action that directly
-     * interacts with the keyguard ui should be posted to this handler, rather
-     * than called directly.
+     * This handler will be associated with the policy thread, which will also be the UI thread of
+     * the keyguard.  Since the apis of the policy, and therefore this class, can be called by other
+     * threads, any action that directly interacts with the keyguard ui should be posted to this
+     * handler, rather than called directly.
      */
-    private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+    private final Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
         @Override
         public void handleMessage(Message msg) {
             String message = "";
@@ -2766,7 +2752,7 @@
 
             try {
                 mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
-            } catch (RemoteException e) {
+            } catch (RemoteException ignored) {
             }
         });
     }
@@ -2790,9 +2776,8 @@
             if (!mSystemReady) {
                 if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
                 return;
-            } else {
-                if (DEBUG) Log.d(TAG, "handleShow");
             }
+            if (DEBUG) Log.d(TAG, "handleShow");
 
             mKeyguardExitAnimationRunner = null;
             mWakeAndUnlocking = false;
@@ -2851,6 +2836,7 @@
     }
 
     private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+        @SuppressLint("MissingPermission")
         @Override
         public void run() {
             Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
@@ -2925,24 +2911,13 @@
             return;
         }
 
-        final String reasonDescription;
-
-        switch(reason) {
-            case WakeAndUnlockUpdateReason.FULFILL:
-                reasonDescription = "fulfilling existing request";
-                break;
-            case WakeAndUnlockUpdateReason.HIDE:
-                reasonDescription = "hiding keyguard";
-                break;
-            case WakeAndUnlockUpdateReason.SHOW:
-                reasonDescription = "showing keyguard";
-                break;
-            case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
-                reasonDescription = "waking to unlock";
-                break;
-            default:
-                throw new IllegalStateException("Unexpected value: " + reason);
-        }
+        final String reasonDescription = switch (reason) {
+            case WakeAndUnlockUpdateReason.FULFILL -> "fulfilling existing request";
+            case WakeAndUnlockUpdateReason.HIDE -> "hiding keyguard";
+            case WakeAndUnlockUpdateReason.SHOW -> "showing keyguard";
+            case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK -> "waking to unlock";
+            default -> throw new IllegalStateException("Unexpected value: " + reason);
+        };
 
         final boolean unsetUnfulfilled = !updatedValue
                 && reason != WakeAndUnlockUpdateReason.FULFILL;
@@ -3057,7 +3032,7 @@
                 IRemoteAnimationFinishedCallback callback =
                         new IRemoteAnimationFinishedCallback() {
                             @Override
-                            public void onAnimationFinished() throws RemoteException {
+                            public void onAnimationFinished() {
                                 if (!KeyguardWmStateRefactor.isEnabled()) {
                                     try {
                                         finishedCallback.onAnimationFinished();
@@ -3542,11 +3517,6 @@
     /**
      * Registers the CentralSurfaces to which the Keyguard View is mounted.
      *
-     * @param centralSurfaces
-     * @param panelView
-     * @param biometricUnlockController
-     * @param notificationContainer
-     * @param bypassController
      * @return the View Controller for the Keyguard View this class is mediating.
      */
     public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
@@ -3773,9 +3743,7 @@
             }
         });
         updateInputRestrictedLocked();
-        mUiBgExecutor.execute(() -> {
-            mTrustManager.reportKeyguardShowingChanged();
-        });
+        mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
     }
 
     private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index e16f8dc..70da3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -90,10 +90,12 @@
 import java.util.concurrent.Executor;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
  * Dagger Module providing keyguard.
  */
+@ExperimentalCoroutinesApi
 @Module(subcomponents = {
         KeyguardQsUserSwitchComponent.class,
         KeyguardStatusBarViewComponent.class,
@@ -115,7 +117,7 @@
      */
     @Provides
     @SysUISingleton
-    public static KeyguardViewMediator newKeyguardViewMediator(
+    static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
             UiEventLogger uiEventLogger,
             SessionTracker sessionTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
index bbdd903..3e6e3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -50,14 +50,13 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : KeyguardQuickAffordanceConfig {
 
-    private val intent: Intent by lazy {
-        cameraIntents.getVideoCameraIntent().apply {
+    private val intent: Intent
+        get() = cameraIntents.getVideoCameraIntent(userTracker.userId).apply {
             putExtra(
                 CameraIntents.EXTRA_LAUNCH_SOURCE,
                 StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
             )
         }
-    }
 
     override val key: String
         get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 9a13558d..b152eea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -22,6 +22,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -174,6 +175,8 @@
                     mainDispatcher
                 ) // keyguardUpdateMonitor requires registration on main thread.
 
+    // TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
+    //  in BiometricStatusRepository
     override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
         get() = conflatedCallbackFlow {
             val callback =
@@ -236,7 +239,8 @@
                         sendUpdateIfFingerprint(
                             biometricSourceType,
                             AcquiredFingerprintAuthenticationStatus(
-                                acquireInfo,
+                                AuthenticationReason.DeviceEntryAuthentication,
+                                acquireInfo
                             ),
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 5e3779a..59288a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -60,6 +60,8 @@
 
     val currentClock: StateFlow<ClockController?>
 
+    val previewClock: StateFlow<ClockController>
+
     val clockEventController: ClockEventController
     fun setClockSize(@ClockSize size: Int)
 }
@@ -120,6 +122,15 @@
                 initialValue = clockRegistry.createCurrentClock()
             )
 
+    override val previewClock: StateFlow<ClockController> =
+        currentClockId
+            .map { clockRegistry.createCurrentClock() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = clockRegistry.createCurrentClock()
+            )
+
     @VisibleForTesting
     suspend fun getClockSize(): SettingsClockSize {
         return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index b1a2297..e017129 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -94,6 +94,7 @@
             context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
         val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
         val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+
         return primaryBouncerInteractor.isBouncerShowing() &&
             sfpsEnabled &&
             sfpsDetectionRunning &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a97c152..0cf74a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -24,15 +24,15 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample as sampleUtil
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.sample
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -62,14 +62,17 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
+    @FlowPreview
     private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
                 // Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
                 // will arrive with a small gap in time. This prevents a transition to LOCKSCREEN
                 // happening prematurely.
-                .onEach { delay(50) }
-                .sample(
+                // This should eventually be removed in favor of
+                // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
+                .sample(150L)
+                .sampleCombine(
                     keyguardInteractor.primaryBouncerShowing,
                     startedKeyguardTransitionStep,
                     powerInteractor.isAwake,
@@ -111,19 +114,20 @@
 
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
-            keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
-                (isKeyguardGoingAway, keyguardState) ->
-                if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                    startTransitionTo(KeyguardState.GONE)
+            keyguardInteractor.isKeyguardGoingAway
+                .sampleUtil(finishedKeyguardState, ::Pair)
+                .collect { (isKeyguardGoingAway, keyguardState) ->
+                    if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-            }
         }
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(startedKeyguardTransitionStep, ::Pair)
+                .sampleUtil(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8fa33ee7..5606d43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,18 +21,18 @@
 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.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -85,15 +85,16 @@
             keyguardInteractor
                 .dozeTransitionTo(DozeStateModel.FINISH)
                 .sample(
-                    combine(
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isKeyguardOccluded,
-                        ::Pair
-                    ),
-                    ::toTriple
+                    startedKeyguardTransitionStep,
+                    keyguardInteractor.isKeyguardOccluded,
+                    keyguardInteractor.biometricUnlockState,
                 )
-                .collect { (_, lastStartedStep, occluded) ->
-                    if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
+                .collect { (_, lastStartedStep, occluded, biometricUnlockState) ->
+                    if (
+                        lastStartedStep.to == KeyguardState.AOD &&
+                            !occluded &&
+                            !isWakeAndUnlock(biometricUnlockState)
+                    ) {
                         val modeOnCanceled =
                             if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
                                 TransitionModeOnCanceled.REVERSE
@@ -126,15 +127,29 @@
     }
 
     private fun listenForAodToGone() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
                 (biometricUnlockState, keyguardState) ->
+                KeyguardWmStateRefactor.assertInLegacyMode()
                 if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
                     startTransitionTo(KeyguardState.GONE)
                 }
             }
         }
     }
+
+    /**
+     * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on
+     * AOD.
+     */
+    fun dismissAod() {
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
+    }
+
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7477624..6b85a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -68,11 +68,11 @@
         scope.launch {
             keyguardInteractor.isKeyguardShowing
                 .sample(
-                    startedKeyguardTransitionStep,
+                    currentKeyguardState,
                     communalInteractor.isIdleOnCommunal,
                 )
-                .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
-                    if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+                .collect { (isKeyguardShowing, currentState, isIdleOnCommunal) ->
+                    if (isKeyguardShowing && currentState == KeyguardState.GONE) {
                         val to =
                             if (isIdleOnCommunal) {
                                 KeyguardState.GLANCEABLE_HUB
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index deb70b7..1da0a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -36,7 +35,6 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
-import dagger.Lazy
 import java.util.UUID
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -64,7 +62,7 @@
     private val shadeRepository: ShadeRepository,
     private val powerInteractor: PowerInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
-    inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
+    private val swipeToDismissInteractor: SwipeToDismissInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.LOCKSCREEN,
@@ -102,49 +100,7 @@
                     return@map null
                 }
 
-                true // TODO(b/278086361): Implement continuous swipe to unlock.
-            }
-            .onStart {
-                // Default to null ("don't care, use a reasonable default").
-                emit(null)
-            }
-            .distinctUntilChanged()
-
-    /**
-     * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
-     * care and should use a reasonable default.
-     */
-    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
-        combine(
-                transitionInteractor.startedKeyguardTransitionStep,
-                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN),
-                inWindowLauncherUnlockAnimationInteractor
-                    .get()
-                    .transitioningToGoneWithInWindowAnimation,
-            ) { startedStep, fromLockscreenStep, transitioningToGoneWithInWindowAnimation ->
-                if (startedStep.to != KeyguardState.GONE) {
-                    // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
-                    // animation).
-                    return@combine null
-                } else if (transitioningToGoneWithInWindowAnimation) {
-                    // If we're prepared for the in-window unlock, we're going to play an animation
-                    // in the window. Make it fully visible.
-                    KeyguardSurfaceBehindModel(
-                        alpha = 1f,
-                    )
-                } else if (fromLockscreenStep.value > 0.5f) {
-                    // Start the animation once we're 50% transitioned to GONE.
-                    KeyguardSurfaceBehindModel(
-                        animateFromAlpha = 0f,
-                        alpha = 1f,
-                        animateFromTranslationY = 500f,
-                        translationY = 0f
-                    )
-                } else {
-                    KeyguardSurfaceBehindModel(
-                        alpha = 0f,
-                    )
-                }
+                true // Make the surface visible during LS -> GONE transitions.
             }
             .onStart {
                 // Default to null ("don't care, use a reasonable default").
@@ -325,6 +281,13 @@
 
     private fun listenForLockscreenToGoneDragging() {
         if (KeyguardWmStateRefactor.isEnabled) {
+            // When the refactor is enabled, we no longer use isKeyguardGoingAway.
+            scope.launch {
+                swipeToDismissInteractor.dismissFling.collect { _ ->
+                    startTransitionTo(KeyguardState.GONE)
+                }
+            }
+
             return
         }
 
@@ -332,6 +295,7 @@
             keyguardInteractor.isKeyguardGoingAway
                 .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
+                    KeyguardWmStateRefactor.assertInLegacyMode()
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
                         startTransitionTo(KeyguardState.GONE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt
index e7d74a5..8ec831c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractor.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.shared.system.smartspace.SmartspaceState
-import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -52,10 +51,7 @@
     val transitioningToGoneWithInWindowAnimation: StateFlow<Boolean> =
         transitionInteractor
             .isInTransitionToState(KeyguardState.GONE)
-            .sample(repository.launcherActivityClass, ::Pair)
-            .map { (isTransitioningToGone, launcherActivityClass) ->
-                isTransitioningToGone && isActivityClassUnderneath(launcherActivityClass)
-            }
+            .map { transitioningToGone -> transitioningToGone && isLauncherUnderneath() }
             .stateIn(scope, SharingStarted.Eagerly, false)
 
     /**
@@ -91,11 +87,11 @@
     }
 
     /**
-     * Whether an activity with the given [activityClass] name is currently underneath the
-     * lockscreen (it's at the top of the activity task stack).
+     * Whether the Launcher is currently underneath the lockscreen (it's at the top of the activity
+     * task stack).
      */
-    private fun isActivityClassUnderneath(activityClass: String?): Boolean {
-        return activityClass?.let {
+    fun isLauncherUnderneath(): Boolean {
+        return repository.launcherActivityClass.value?.let {
             activityManager.runningTask?.topActivity?.className?.equals(it)
         }
             ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 356c408..196770a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -44,6 +44,8 @@
 
     val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock
 
+    val previewClock: StateFlow<ClockController> = keyguardClockRepository.previewClock
+
     var clock: ClockController? by keyguardClockRepository.clockEventController::clock
 
     val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 922baa3..c496a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -16,75 +16,87 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.toPx
+import dagger.Lazy
 import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+
+/**
+ * Distance over which the surface behind the keyguard is animated in during a Y-translation
+ * animation.
+ */
+const val SURFACE_TRANSLATION_Y_DISTANCE_DP = 250
 
 @SysUISingleton
 class KeyguardSurfaceBehindInteractor
 @Inject
 constructor(
     private val repository: KeyguardSurfaceBehindRepository,
-    private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
-    private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    context: Context,
     transitionInteractor: KeyguardTransitionInteractor,
+    inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
+    swipeToDismissInteractor: SwipeToDismissInteractor,
+    notificationLaunchInteractor: NotificationLaunchAnimationInteractor,
 ) {
-
-    @OptIn(ExperimentalCoroutinesApi::class)
+    /**
+     * The view params to use for the surface. These params describe the alpha/translation values to
+     * apply, as well as animation parameters if necessary.
+     */
     val viewParams: Flow<KeyguardSurfaceBehindModel> =
-        transitionInteractor.isInTransitionToAnyState
-            .flatMapLatest { isInTransition ->
-                if (!isInTransition) {
-                    defaultParams
-                } else {
-                    combine(
-                        transitionSpecificViewParams,
-                        defaultParams,
-                    ) { transitionParams, defaultParams ->
-                        transitionParams ?: defaultParams
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.currentKeyguardState,
+                notificationLaunchInteractor.isLaunchAnimationRunning,
+            ) { startedStep, currentState, notifAnimationRunning ->
+                // If we're in transition to GONE, special unlock animation params apply.
+                if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
+                    if (notifAnimationRunning) {
+                        // If the notification launch animation is running, leave the alpha at 0f.
+                        // The ActivityLaunchAnimator will morph it from the notification at the
+                        // appropriate time.
+                        return@combine KeyguardSurfaceBehindModel(
+                            alpha = 0f,
+                        )
+                    } else if (
+                        inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()
+                    ) {
+                        // The Launcher icons have their own translation/alpha animations during the
+                        // in-window animation. We'll just make the surface visible and let Launcher
+                        // do its thing.
+                        return@combine KeyguardSurfaceBehindModel(
+                            alpha = 1f,
+                        )
+                    } else {
+                        // Otherwise, animate a surface in via alpha/translation, and apply the
+                        // swipe velocity (if available) to the translation spring.
+                        return@combine KeyguardSurfaceBehindModel(
+                            animateFromAlpha = 0f,
+                            alpha = 1f,
+                            animateFromTranslationY =
+                                SURFACE_TRANSLATION_Y_DISTANCE_DP.toPx(context).toFloat(),
+                            translationY = 0f,
+                            startVelocity = swipeToDismissInteractor.dismissFling.value?.velocity
+                                    ?: 0f,
+                        )
                     }
                 }
+
+                // Default to the visibility of the current state, with no animations.
+                KeyguardSurfaceBehindModel(alpha = if (isSurfaceVisible(currentState)) 1f else 0f)
             }
             .distinctUntilChanged()
 
     val isAnimatingSurface = repository.isAnimatingSurface
 
-    private val defaultParams =
-        transitionInteractor.finishedKeyguardState.map { state ->
-            KeyguardSurfaceBehindModel(
-                alpha =
-                    if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
-                    else 0f
-            )
-        }
-
-    /**
-     * View params provided by the transition interactor for the most recently STARTED transition.
-     * This is used to run transition-specific animations on the surface.
-     *
-     * If null, there are no transition-specific view params needed for this transition and we will
-     * use a reasonable default.
-     */
-    @OptIn(ExperimentalCoroutinesApi::class)
-    private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
-        transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
-            when (startedStep.from) {
-                KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
-                KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
-                // Return null for other states, where no transition specific params are needed.
-                else -> flowOf(null)
-            }
-        }
-
     fun setAnimatingSurface(animating: Boolean) {
         repository.setAnimatingSurface(animating)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b43ab5e..310f13d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -60,6 +60,7 @@
     private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
     private val fromPrimaryBouncerTransitionInteractor:
         dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
+    private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
 ) {
     private val TAG = this::class.simpleName
 
@@ -346,6 +347,7 @@
         when (val startedState = startedKeyguardState.replayCache.last()) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            AOD -> fromAodTransitionInteractor.get().dismissAod()
             else ->
                 Log.e(
                     "KeyguardTransitionInteractor",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 19d00cf..c7f262a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -42,7 +42,7 @@
     private val lightRevealScrimRepository: LightRevealScrimRepository,
     @Application private val scope: CoroutineScope,
     private val scrimLogger: ScrimLogger,
-    powerInteractor: PowerInteractor,
+    private val powerInteractor: PowerInteractor,
 ) {
 
     init {
@@ -83,11 +83,13 @@
             // (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
             // correct end states are respected even if the screen turned off (or was still off)
             // when the animation finished
-            powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF ||
-                it == 1f ||
-                it == 0f
+            screenIsShowingContent() || it == 1f || it == 0f
         }
 
+    private fun screenIsShowingContent() =
+        powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+            powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+
     companion object {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
new file mode 100644
index 0000000..86e4115
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable
+ * lockscreen to unlock the device.
+ */
+@SysUISingleton
+class SwipeToDismissInteractor
+@Inject
+constructor(
+    @Background backgroundScope: CoroutineScope,
+    shadeRepository: ShadeRepository,
+    transitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    /**
+     * Emits a [FlingInfo] whenever a swipe to dismiss gesture has started a fling animation on the
+     * lockscreen while it's dismissable.
+     *
+     * This value is collected by [FromLockscreenTransitionInteractor] to start a transition from
+     * LOCKSCREEN -> GONE, and by [KeyguardSurfaceBehindInteractor] to match the surface remote
+     * animation's velocity to the fling velocity, if applicable.
+     */
+    val dismissFling =
+        shadeRepository.currentFling
+            .sample(
+                transitionInteractor.startedKeyguardState,
+                keyguardInteractor.isKeyguardDismissible
+            )
+            .filter { (flingInfo, startedState, keyguardDismissable) ->
+                flingInfo != null &&
+                    !flingInfo.expand &&
+                    startedState == KeyguardState.LOCKSCREEN &&
+                    keyguardDismissable
+            }
+            .map { (flingInfo, _) -> flingInfo }
+            .stateIn(backgroundScope, SharingStarted.Eagerly, null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 5c2df45..3ccbdba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -60,6 +60,7 @@
     // The following are MutableSharedFlows, and do not require flowOn
     val startedKeyguardState = transitionInteractor.startedKeyguardState
     val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+    val currentKeyguardState = transitionInteractor.currentKeyguardState
 
     suspend fun startTransitionTo(
         toState: KeyguardState,
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 49af664..b81793e 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
@@ -19,6 +19,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -26,7 +28,6 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import javax.inject.Inject
 
 @SysUISingleton
 class WindowManagerLockscreenVisibilityInteractor
@@ -37,6 +38,7 @@
     surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
     fromLockscreenInteractor: FromLockscreenTransitionInteractor,
     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
 ) {
     private val defaultSurfaceBehindVisibility =
         transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -72,8 +74,7 @@
      */
     @OptIn(ExperimentalCoroutinesApi::class)
     val surfaceBehindVisibility: Flow<Boolean> =
-        transitionInteractor
-                .isInTransitionToAnyState
+        transitionInteractor.isInTransitionToAnyState
             .flatMapLatest { isInTransition ->
                 if (!isInTransition) {
                     defaultSurfaceBehindVisibility
@@ -99,12 +100,16 @@
         combine(
                 transitionInteractor.isInTransitionToState(KeyguardState.GONE),
                 transitionInteractor.finishedKeyguardState,
-                surfaceBehindInteractor.isAnimatingSurface
-            ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+                surfaceBehindInteractor.isAnimatingSurface,
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+                // Using the animation if we're animating it directly, or if the
+                // ActivityLaunchAnimator is in the process of animating it.
+                val animationsRunning = isAnimatingSurface || notifLaunchRunning
                 // We may still be animating the surface after the keyguard is fully GONE, since
                 // some animations (like the translation spring) are not tied directly to the
                 // transition step amount.
-                isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
             }
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
new file mode 100644
index 0000000..7f0b483
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the compose lockscreen flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ComposeLockscreen {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable()
+
+    /**
+     * 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/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index cc385a8..474de77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
 import android.hardware.fingerprint.FingerprintManager
 import android.os.SystemClock.elapsedRealtime
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 
 /**
  * Fingerprint authentication status provided by
@@ -40,8 +41,10 @@
 ) : FingerprintAuthenticationStatus()
 
 /** Fingerprint acquired message. */
-data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
-    FingerprintAuthenticationStatus() {
+data class AcquiredFingerprintAuthenticationStatus(
+    val authenticationReason: AuthenticationReason,
+    val acquiredInfo: Int
+) : FingerprintAuthenticationStatus() {
 
     val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
index 7fb5cfd..aad8a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -44,6 +44,9 @@
      * running, in which case we'll animate from the current value to [translationY].
      */
     val animateFromTranslationY: Float = translationY,
+
+    /** Velocity with which to start the Y-translation spring animation. */
+    val startVelocity: Float = 0f,
 ) {
     fun willAnimateAlpha(): Boolean {
         return animateFromAlpha != alpha
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/LockscreenSceneBlueprint.kt
similarity index 60%
copy from packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/LockscreenSceneBlueprint.kt
index 6d9cba4..2eafb83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/LockscreenSceneBlueprint.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.composable.blueprint
+package com.android.systemui.keyguard.shared.model
 
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.SceneScope
-
-/** Defines interface for classes that can render the content for a specific blueprint/layout. */
+/**
+ * Defines interface for classes that can render the content for a specific blueprint/layout.
+ *
+ * The actual rendering is done by a compose-aware sub-interface.
+ */
 interface LockscreenSceneBlueprint {
-
     /** The ID that uniquely identifies this blueprint across all other blueprints. */
     val id: String
-
-    /** Renders the content of this blueprint. */
-    @Composable fun SceneScope.Content(modifier: Modifier)
 }
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 f0e89f9..62a6e8b 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
@@ -18,7 +18,6 @@
 
 import android.transition.TransitionManager
 import android.transition.TransitionSet
-import android.util.Log
 import android.view.View.INVISIBLE
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.helper.widget.Layer
@@ -36,7 +35,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
 import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
 import kotlinx.coroutines.launch
 
@@ -78,10 +76,6 @@
                 launch {
                     if (!migrateClocksToBlueprint()) return@launch
                     viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
-                        Log.d(
-                            "ClockViewBinder",
-                            "Sherry clockShouldBeCentered $clockShouldBeCentered"
-                        )
                         viewModel.clock?.let {
                             // Weather clock also has hasCustomPositionUpdatedAnimation as true
                             // TODO(b/323020908): remove ID check
@@ -169,16 +163,12 @@
         rootView: ConstraintLayout,
     ) {
         clockController?.let { clock ->
-            clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
-            if (clock.largeClock.layout.views.size == 1) {
-                clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large
-            }
-            // small clock should either be a single view or container with id
-            // `lockscreen_clock_view`
             clock.smallClock.layout.views.forEach {
                 rootView.addView(it).apply { it.visibility = INVISIBLE }
             }
-            clock.largeClock.layout.views.forEach { rootView.addView(it) }
+            clock.largeClock.layout.views.forEach {
+                rootView.addView(it).apply { it.visibility = INVISIBLE }
+            }
         }
     }
     fun applyConstraints(
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 1b5b329..b56c998 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,12 +17,35 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.content.Context
 import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ClockEventController
+import com.android.systemui.customization.R as customizationR
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
+import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
 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.res.R
+import com.android.systemui.util.Utils
+import kotlin.reflect.KSuspendFunction1
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
 
 /** Binder for the small clock view, large clock view. */
 object KeyguardPreviewClockViewBinder {
@@ -45,4 +68,129 @@
             }
         }
     }
+
+    @JvmStatic
+    fun bind(
+        context: Context,
+        rootView: ConstraintLayout,
+        viewModel: KeyguardPreviewClockViewModel,
+        clockEventController: ClockEventController,
+        updateClockAppearance: KSuspendFunction1<ClockController, Unit>
+    ) {
+        rootView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    combine(viewModel.selectedClockSize, viewModel.previewClock) { _, clock ->
+                            clock
+                        }
+                        .collect { previewClock ->
+                            viewModel.lastClock?.let { lastClock ->
+                                (lastClock.largeClock.layout.views +
+                                        lastClock.smallClock.layout.views)
+                                    .forEach { rootView.removeView(it) }
+                            }
+                            viewModel.lastClock = previewClock
+                            clockEventController.clock = previewClock
+                            updateClockAppearance(previewClock)
+
+                            if (viewModel.shouldHighlightSelectedAffordance) {
+                                (previewClock.largeClock.layout.views +
+                                        previewClock.smallClock.layout.views)
+                                    .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
+                            }
+                            previewClock.largeClock.layout.views.forEach {
+                                (it.parent as? ViewGroup)?.removeView(it)
+                                rootView.addView(it)
+                            }
+
+                            previewClock.smallClock.layout.views.forEach {
+                                (it.parent as? ViewGroup)?.removeView(it)
+                                rootView.addView(it)
+                            }
+                            applyPreviewConstraints(context, rootView, viewModel)
+                        }
+                }
+            }
+        }
+    }
+
+    private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
+        constraints.apply {
+            constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+            constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+            val largeClockTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+                    context.resources.getDimensionPixelSize(
+                        customizationR.dimen.small_clock_padding_top
+                    ) +
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.keyguard_smartspace_top_offset
+                    ) +
+                    getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
+                    getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
+            connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+            connect(
+                R.id.lockscreen_clock_view_large,
+                ConstraintSet.END,
+                PARENT_ID,
+                ConstraintSet.END
+            )
+
+            connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+            constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+            constrainHeight(
+                R.id.lockscreen_clock_view,
+                context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
+            )
+            connect(
+                R.id.lockscreen_clock_view,
+                START,
+                PARENT_ID,
+                START,
+                context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            )
+            val smallClockTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+                    Utils.getStatusBarHeaderHeightKeyguard(context)
+            connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+        }
+    }
+
+    private fun applyPreviewConstraints(
+        context: Context,
+        rootView: ConstraintLayout,
+        viewModel: KeyguardPreviewClockViewModel
+    ) {
+        val cs = ConstraintSet().apply { clone(rootView) }
+        val clock = viewModel.previewClock.value
+        applyClockDefaultConstraints(context, cs)
+        clock.largeClock.layout.applyPreviewConstraints(cs)
+        clock.smallClock.layout.applyPreviewConstraints(cs)
+
+        // When selectedClockSize is the initial value, make both clocks invisible to avoid
+        // flickering
+        val largeClockVisibility =
+            when (viewModel.selectedClockSize.value) {
+                SettingsClockSize.DYNAMIC -> VISIBLE
+                SettingsClockSize.SMALL -> INVISIBLE
+                null -> INVISIBLE
+            }
+        val smallClockVisibility =
+            when (viewModel.selectedClockSize.value) {
+                SettingsClockSize.DYNAMIC -> INVISIBLE
+                SettingsClockSize.SMALL -> VISIBLE
+                null -> INVISIBLE
+            }
+
+        cs.apply {
+            setVisibility(clock.largeClock.layout.views, largeClockVisibility)
+            setVisibility(clock.smallClock.layout.views, smallClockVisibility)
+        }
+        cs.applyTo(rootView)
+    }
+
+    private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
+    private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 48092c6..789d30f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -191,6 +191,7 @@
                                 .collect { y ->
                                     childViews[burnInLayerId]?.translationY = y
                                     childViews[largeClockId]?.translationY = y
+                                    childViews[aodNotificationIconContainerId]?.translationY = y
                                 }
                         }
 
@@ -200,6 +201,7 @@
                                 .collect { x ->
                                     childViews[burnInLayerId]?.translationX = x
                                     childViews[largeClockId]?.translationX = x
+                                    childViews[aodNotificationIconContainerId]?.translationX = x
                                 }
                         }
 
@@ -219,6 +221,10 @@
                                         // transition with other parts in burnInLayer
                                         childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
                                         childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+                                        childViews[aodNotificationIconContainerId]?.scaleX =
+                                            scaleViewModel.scale
+                                        childViews[aodNotificationIconContainerId]?.scaleY =
+                                            scaleViewModel.scale
                                     }
                                 }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
index 8587022..fb6efd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -67,8 +67,8 @@
         SpringAnimation(animatedTranslationY).apply {
             spring =
                 SpringForce().apply {
-                    stiffness = 200f
-                    dampingRatio = 1f
+                    stiffness = 275f
+                    dampingRatio = 0.98f
                 }
             addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
             addEndListener { _, _, _, _ ->
@@ -84,7 +84,7 @@
     private var animatedAlpha = 0f
     private var alphaAnimator =
         ValueAnimator.ofFloat(0f, 1f).apply {
-            duration = 500
+            duration = 150
             interpolator = Interpolators.ALPHA_IN
             addUpdateListener {
                 animatedAlpha = it.animatedValue as Float
@@ -162,6 +162,7 @@
                 // If the spring isn't running yet, set the start value. Otherwise, respect the
                 // current position.
                 animatedTranslationY.value = viewParams.animateFromTranslationY
+                translateYSpring.setStartVelocity(viewParams.startVelocity)
             }
 
             translateYSpring.animateToFinalPosition(viewParams.translationY)
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 a0c0095..c14917b 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
@@ -46,6 +46,7 @@
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -197,6 +198,9 @@
                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
             )
         }
+        if (migrateClocksToBlueprint()) {
+            clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
+        }
         runBlocking(mainDispatcher) {
             host =
                 SurfaceControlViewHost(
@@ -396,11 +400,21 @@
 
         if (!shouldHideClock) {
             setUpClock(previewContext, rootView)
-            KeyguardPreviewClockViewBinder.bind(
-                largeClockHostView,
-                smallClockHostView,
-                clockViewModel,
-            )
+            if (migrateClocksToBlueprint()) {
+                KeyguardPreviewClockViewBinder.bind(
+                    context,
+                    keyguardRootView,
+                    clockViewModel,
+                    clockController,
+                    ::updateClockAppearance
+                )
+            } else {
+                KeyguardPreviewClockViewBinder.bind(
+                    largeClockHostView,
+                    smallClockHostView,
+                    clockViewModel,
+                )
+            }
         }
 
         setUpSmartspace(previewContext, rootView)
@@ -474,55 +488,61 @@
 
     private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
         val resources = parentView.resources
-        largeClockHostView = FrameLayout(previewContext)
-        largeClockHostView.layoutParams =
-            FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.MATCH_PARENT,
-                FrameLayout.LayoutParams.MATCH_PARENT,
-            )
-        parentView.addView(largeClockHostView)
-        largeClockHostView.isInvisible = true
+        if (!migrateClocksToBlueprint()) {
+            largeClockHostView = FrameLayout(previewContext)
+            largeClockHostView.layoutParams =
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                )
+            largeClockHostView.isInvisible = true
+            parentView.addView(largeClockHostView)
 
-        smallClockHostView = FrameLayout(previewContext)
-        val layoutParams =
-            FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.WRAP_CONTENT,
-                resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_height
+            smallClockHostView = FrameLayout(previewContext)
+            val layoutParams =
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.WRAP_CONTENT,
+                    resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_height
+                    )
                 )
+            layoutParams.topMargin =
+                KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+                    resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_padding_top
+                    )
+            smallClockHostView.layoutParams = layoutParams
+            smallClockHostView.setPaddingRelative(
+                /* start = */ resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.clock_padding_start
+                ),
+                /* top = */ 0,
+                /* end = */ 0,
+                /* bottom = */ 0
             )
-        layoutParams.topMargin =
-            KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
-                resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_padding_top
-                )
-        smallClockHostView.layoutParams = layoutParams
-        smallClockHostView.setPaddingRelative(
-            resources.getDimensionPixelSize(
-                com.android.systemui.customization.R.dimen.clock_padding_start
-            ),
-            0,
-            0,
-            0
-        )
-        smallClockHostView.clipChildren = false
-        parentView.addView(smallClockHostView)
-        smallClockHostView.isInvisible = true
+            smallClockHostView.clipChildren = false
+            parentView.addView(smallClockHostView)
+            smallClockHostView.isInvisible = true
+        }
 
         // TODO (b/283465254): Move the listeners to KeyguardClockRepository
-        val clockChangeListener =
-            object : ClockRegistry.ClockChangeListener {
-                override fun onCurrentClockChanged() {
-                    onClockChanged()
+        if (!migrateClocksToBlueprint()) {
+            val clockChangeListener =
+                object : ClockRegistry.ClockChangeListener {
+                    override fun onCurrentClockChanged() {
+                        onClockChanged()
+                    }
                 }
-            }
-        clockRegistry.registerClockChangeListener(clockChangeListener)
-        disposables.add(
-            DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
-        )
+            clockRegistry.registerClockChangeListener(clockChangeListener)
+            disposables.add(
+                DisposableHandle {
+                    clockRegistry.unregisterClockChangeListener(clockChangeListener)
+                }
+            )
 
-        clockController.registerListeners(parentView)
-        disposables.add(DisposableHandle { clockController.unregisterListeners() })
+            clockController.registerListeners(parentView)
+            disposables.add(DisposableHandle { clockController.unregisterListeners() })
+        }
 
         val receiver =
             object : BroadcastReceiver() {
@@ -542,50 +562,61 @@
         )
         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
 
-        val layoutChangeListener =
-            View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
-                if (clockController.clock !is DefaultClockController) {
-                    clockController.clock
-                        ?.largeClock
-                        ?.events
-                        ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
-                    clockController.clock
-                        ?.smallClock
-                        ?.events
-                        ?.onTargetRegionChanged(KeyguardClockSwitch.getSmallClockRegion(parentView))
+        if (!migrateClocksToBlueprint()) {
+            val layoutChangeListener =
+                View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+                    if (clockController.clock !is DefaultClockController) {
+                        clockController.clock
+                            ?.largeClock
+                            ?.events
+                            ?.onTargetRegionChanged(
+                                KeyguardClockSwitch.getLargeClockRegion(parentView)
+                            )
+                        clockController.clock
+                            ?.smallClock
+                            ?.events
+                            ?.onTargetRegionChanged(
+                                KeyguardClockSwitch.getSmallClockRegion(parentView)
+                            )
+                    }
                 }
-            }
-        parentView.addOnLayoutChangeListener(layoutChangeListener)
-        disposables.add(
-            DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
-        )
+            parentView.addOnLayoutChangeListener(layoutChangeListener)
+            disposables.add(
+                DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
+            )
+        }
 
         onClockChanged()
     }
 
+    private suspend fun updateClockAppearance(clock: ClockController) {
+        clockController.clock = clock
+        val colors = wallpaperColors
+        if (clockRegistry.seedColor == null && colors != null) {
+            // 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.
+            val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
+            val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
+            val lightClockColor = wallpaperColorScheme.accent1.s100
+            val darkClockColor = wallpaperColorScheme.accent2.s600
+
+            // Note that when [wallpaperColors] is null, isWallpaperDark is true.
+            val isWallpaperDark: Boolean =
+                (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+            clock.events.onSeedColorChanged(
+                if (isWallpaperDark) lightClockColor else darkClockColor
+            )
+        }
+    }
     private fun onClockChanged() {
+        if (migrateClocksToBlueprint()) {
+            return
+        }
         coroutineScope.launch {
             val clock = clockRegistry.createCurrentClock()
             clockController.clock = clock
-
-            val colors = wallpaperColors
-            if (clockRegistry.seedColor == null && colors != null) {
-                // 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.
-                val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
-                val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
-                val lightClockColor = wallpaperColorScheme.accent1.s100
-                val darkClockColor = wallpaperColorScheme.accent2.s600
-
-                // Note that when [wallpaperColors] is null, isWallpaperDark is true.
-                val isWallpaperDark: Boolean =
-                    (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
-                clock.events.onSeedColorChanged(
-                    if (isWallpaperDark) lightClockColor else darkClockColor
-                )
-            }
-
+            updateClockAppearance(clock)
             updateLargeClock(clock)
             updateSmallClock(clock)
         }
@@ -626,6 +657,9 @@
     }
 
     private fun updateLargeClock(clock: ClockController) {
+        if (migrateClocksToBlueprint()) {
+            return
+        }
         clock.largeClock.events.onTargetRegionChanged(
             KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
         )
@@ -637,6 +671,9 @@
     }
 
     private fun updateSmallClock(clock: ClockController) {
+        if (migrateClocksToBlueprint()) {
+            return
+        }
         clock.smallClock.events.onTargetRegionChanged(
             KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
         )
@@ -656,6 +693,6 @@
         private const val KEY_DISPLAY_ID = "display_id"
         private const val KEY_COLORS = "wallpaper_colors"
 
-        private const val DIM_ALPHA = 0.3f
+        const val DIM_ALPHA = 0.3f
     }
 }
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 3d36eb0..9a1fcc1 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
@@ -41,11 +41,13 @@
             return
         }
 
-        val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+        // The burn-in layer requires at least 1 view at all times
+        val emptyView = View(context, null).apply { id = View.generateViewId() }
+        constraintLayout.addView(emptyView)
         burnInLayer =
             AodBurnInLayer(context).apply {
                 id = R.id.burn_in_layer
-                addView(nic)
+                addView(emptyView)
                 if (!migrateClocksToBlueprint()) {
                     val statusView =
                         constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
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 b1178f5..631b342 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
@@ -201,13 +201,16 @@
     }
 
     private fun getDimen(name: String): Int {
-        val res = context.packageManager.getResourcesForApplication(context.packageName)
-        val id = res.getIdentifier(name, "dimen", context.packageName)
-        return res.getDimensionPixelSize(id)
+        return getDimen(context, name)
     }
 
     companion object {
         private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
         private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+        fun getDimen(context: Context, name: String): Int {
+            val res = context.packageManager.getResourcesForApplication(context.packageName)
+            val id = res.getIdentifier(name, "dimen", context.packageName)
+            return res.getDimensionPixelSize(id)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index a651c10..52d94a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -57,7 +57,7 @@
     private val mainDispatcher: CoroutineDispatcher,
 ) : KeyguardSection() {
     private val placeHolderId = R.id.nssl_placeholder
-    private var disposableHandle: DisposableHandle? = null
+    private val disposableHandles: MutableList<DisposableHandle> = mutableListOf()
 
     /**
      * Align the notification placeholder bottom to the top of either the lock icon or the ambient
@@ -102,8 +102,9 @@
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
         }
-        disposableHandle?.dispose()
-        disposableHandle =
+
+        disposeHandles()
+        disposableHandles.add(
             SharedNotificationContainerBinder.bind(
                 sharedNotificationContainer,
                 sharedNotificationContainerViewModel,
@@ -112,19 +113,28 @@
                 notificationStackSizeCalculator,
                 mainDispatcher,
             )
+        )
+
         if (sceneContainerFlags.flexiNotifsEnabled()) {
-            NotificationStackAppearanceViewBinder.bind(
-                context,
-                sharedNotificationContainer,
-                notificationStackAppearanceViewModel,
-                ambientState,
-                controller,
+            disposableHandles.add(
+                NotificationStackAppearanceViewBinder.bind(
+                    context,
+                    sharedNotificationContainer,
+                    notificationStackAppearanceViewModel,
+                    ambientState,
+                    controller,
+                )
             )
         }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
-        disposableHandle?.dispose()
+        disposeHandles()
         constraintLayout.removeView(placeHolderId)
     }
+
+    private fun disposeHandles() {
+        disposableHandles.forEach { it.dispose() }
+        disposableHandles.clear()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 3737e6f..d26356e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
@@ -46,6 +47,14 @@
             to = KeyguardState.GONE,
         )
 
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStep = { 1 - it },
+            onFinish = { 0f },
+            onCancel = { 1f },
+        )
+
     /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
         bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index d4ea728..9cf3c95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -28,7 +28,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
 
 /** Models UI state for the alpha of the AOD (always-on display). */
 @SysUISingleton
@@ -43,15 +42,13 @@
     /** The alpha level for the entire lockscreen while in AOD. */
     val alpha: Flow<Float> =
         combine(
-                keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart {
-                    emit(0f)
-                },
+                keyguardTransitionInteractor.currentKeyguardState,
                 merge(
                     keyguardInteractor.keyguardAlpha,
                     occludedToLockscreenTransitionViewModel.lockscreenAlpha,
                 )
-            ) { transitionToGone, alpha ->
-                if (transitionToGone == 1f) {
+            ) { currentKeyguardState, alpha ->
+                if (currentKeyguardState == KeyguardState.GONE) {
                     // Ensures content is not visible when in GONE state
                     0f
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 780e323..828e033 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -28,6 +28,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import javax.inject.Inject
@@ -117,7 +120,10 @@
     ): Flow<BurnInModel> {
         return combine(
             merge(
-                    keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+                    keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
+                    keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
+                        it.value
+                    },
                     keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
                 )
                 .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index ba04fd3..f5e6135 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -67,6 +67,8 @@
             duration = 500.milliseconds,
             onStart = { 0f },
             onStep = { it },
+            onFinish = { 1f },
+            onCancel = { 1f },
         )
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
index 5301302..f95a8a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -20,9 +20,14 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** View model for the small clock view, large clock view. */
 class KeyguardPreviewClockViewModel
@@ -30,11 +35,24 @@
 constructor(
     @Application private val context: Context,
     interactor: KeyguardClockInteractor,
+    @Application private val applicationScope: CoroutineScope,
 ) {
 
+    var shouldHighlightSelectedAffordance: Boolean = false
     val isLargeClockVisible: Flow<Boolean> =
         interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
 
     val isSmallClockVisible: Flow<Boolean> =
         interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+
+    var lastClock: ClockController? = null
+
+    val previewClock: StateFlow<ClockController> = interactor.previewClock
+
+    val selectedClockSize: StateFlow<SettingsClockSize?> =
+        interactor.selectedClockSize.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 709e184..f8a12bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -61,6 +61,9 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
     aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+    alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+    primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
     lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
     glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     screenOffAnimationController: ScreenOffAnimationController,
@@ -92,10 +95,15 @@
     val alpha: Flow<Float> =
         combine(
                 communalInteractor.isIdleOnCommunal,
+                // The transitions are mutually exclusive, so they are safe to merge to get the last
+                // value emitted by any of them. Do not add flows that cannot make this guarantee.
                 merge(
                     aodAlphaViewModel.alpha,
                     lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
                     glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+                    lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+                    primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+                    alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
                 )
             ) { isIdleOnCommunal, alpha ->
                 if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index a26ef07..d981650 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -46,12 +46,14 @@
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
-            duration = 250.milliseconds,
+            duration = 200.milliseconds,
             onStep = { 1 - it },
             onFinish = { 0f },
             onCancel = { 1f },
         )
 
+    val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index ca9c857..67c42f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -22,8 +22,10 @@
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.addListener
 import com.android.systemui.Flags
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
@@ -34,6 +36,7 @@
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
@@ -49,10 +52,12 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.launch
 
@@ -62,7 +67,8 @@
 @Inject
 constructor(
     private val context: Context,
-    private val fpAuthRepository: DeviceEntryFingerprintAuthInteractor,
+    private val biometricStatusInteractor: BiometricStatusInteractor,
+    private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
     // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
     //  DozeInteractor as DozeServiceHost already depends on DozeInteractor.
@@ -86,6 +92,23 @@
     private val additionalSensorLengthPadding =
         context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
 
+    // Merged [FingerprintAuthenticationStatus] from BiometricPrompt acquired messages and
+    // device entry authentication messages
+    private val mergedFingerprintAuthenticationStatus =
+        merge(
+                biometricStatusInteractor.fingerprintAcquiredStatus,
+                deviceEntryFingerprintAuthInteractor.authenticationStatus
+            )
+            .filter {
+                if (it is AcquiredFingerprintAuthenticationStatus) {
+                    it.authenticationReason == AuthenticationReason.DeviceEntryAuthentication ||
+                        it.authenticationReason ==
+                            AuthenticationReason.BiometricPromptAuthentication
+                } else {
+                    true
+                }
+            }
+
     val isVisible: Flow<Boolean> = _visible.asStateFlow()
 
     val progress: Flow<Float> = _progress.asStateFlow()
@@ -147,7 +170,14 @@
                 viewLeftTop
             }
 
-    val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+    val isFingerprintAuthRunning: Flow<Boolean> =
+        combine(
+            deviceEntryFingerprintAuthInteractor.isRunning,
+            biometricStatusInteractor.sfpsAuthenticationReason
+        ) { deviceEntryAuthIsRunning, sfpsAuthReason ->
+            deviceEntryAuthIsRunning ||
+                sfpsAuthReason == AuthenticationReason.BiometricPromptAuthentication
+        }
 
     val rotation: Flow<Float> =
         combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
@@ -185,7 +215,8 @@
                     sfpsSensorInteractor.authenticationDuration
                         .flatMapLatest { authDuration ->
                             _animator?.cancel()
-                            fpAuthRepository.authenticationStatus.map { authStatus ->
+                            mergedFingerprintAuthenticationStatus.map {
+                                authStatus: FingerprintAuthenticationStatus ->
                                 when (authStatus) {
                                     is AcquiredFingerprintAuthenticationStatus -> {
                                         if (authStatus.fingerprintCaptureStarted) {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
similarity index 60%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
index a368de2..fe7dc4b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.log.dagger
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import javax.inject.Qualifier
+
+/** A [TableLogBuffer] for communal-related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3e00940..ac579d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -579,6 +579,16 @@
         return factory.create("CommunalLog", 250);
     }
 
+    /**
+     * Provides a {@link TableLogBuffer} for communal-related logs.
+     */
+    @Provides
+    @SysUISingleton
+    @CommunalTableLog
+    public static TableLogBuffer provideCommunalTableLogBuffer(TableLogBufferFactory factory) {
+        return factory.create("CommunalTableLog", 250);
+    }
+
     /** Provides a {@link LogBuffer} for display metrics related logs. */
     @Provides
     @SysUISingleton
@@ -618,4 +628,13 @@
     public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
         return factory.create("PackageChangeRepo", 50);
     }
+
+    /** Provides a {@link LogBuffer} for NavBarButtonClicks. */
+    @Provides
+    @SysUISingleton
+    @NavBarButtonClickLog
+    public static LogBuffer provideNavBarButtonClickLogBuffer(LogBufferFactory factory) {
+        return factory.create("NavBarButtonClick", 50);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
new file mode 100644
index 0000000..939dab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.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.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavBarButtonClickLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 3f4f347..8d918e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -22,9 +22,12 @@
 import androidx.annotation.UiThread
 import androidx.lifecycle.Observer
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceStateLogger
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.res.R
 import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
+
+private const val TAG = "SeekBarObserver"
 
 /**
  * Observer for changes from SeekBarViewModel.
@@ -39,6 +42,10 @@
         @JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
     }
 
+    // Trace state loggers for playing and listening states of progress bar.
+    private val playingStateLogger = TraceStateLogger("$TAG#playing")
+    private val listeningStateLogger = TraceStateLogger("$TAG#listening")
+
     val seekBarEnabledMaxHeight =
         holder.seekBar.context.resources.getDimensionPixelSize(
             R.dimen.qs_media_enabled_seekbar_height
@@ -103,9 +110,13 @@
             return
         }
 
+        playingStateLogger.log("${data.playing}")
+        listeningStateLogger.log("${data.listening}")
+
         holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
         holder.seekBar.isEnabled = data.seekAvailable
-        progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
+        progressDrawable?.animate =
+            data.playing && !data.scrubbing && animationEnabled && data.listening
         progressDrawable?.transitionEnabled = !data.seekAvailable
 
         if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index a91917a..40a9b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -84,7 +84,16 @@
     @Background private val bgExecutor: RepeatableExecutor,
     private val falsingManager: FalsingManager,
 ) {
-    private var _data = Progress(false, false, false, false, null, 0)
+    private var _data =
+        Progress(
+            enabled = false,
+            seekAvailable = false,
+            playing = false,
+            scrubbing = false,
+            elapsedTime = null,
+            duration = 0,
+            listening = false
+        )
         set(value) {
             val enabledChanged = value.enabled != field.enabled
             field = value
@@ -239,7 +248,7 @@
             )
                 false
             else true
-        _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
+        _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
         checkIfPollingNeeded()
     }
 
@@ -258,6 +267,7 @@
                 scrubbing = false,
                 elapsedTime = position,
                 duration = 100,
+                listening = false,
             )
     }
 
@@ -548,9 +558,12 @@
     data class Progress(
         val enabled: Boolean,
         val seekAvailable: Boolean,
+        /** whether playback state is not paused or connecting */
         val playing: Boolean,
         val scrubbing: Boolean,
         val elapsedTime: Int?,
-        val duration: Int
+        val duration: Int,
+        /** whether seekBar is listening to progress updates */
+        val listening: Boolean,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index 785a1e8..1d3cfd2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -30,7 +30,7 @@
     private val localBluetoothManager: LocalBluetoothManager?
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
-    fun create(packageName: String): LocalMediaManager {
+    fun create(packageName: String?): LocalMediaManager {
         return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
             .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 02f0d12..038582c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -26,14 +26,15 @@
 import androidx.core.view.GestureDetectorCompat
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.tracing.TraceStateLogger
 import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
+import com.android.systemui.res.R
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.animation.PhysicsAnimator
 
@@ -42,6 +43,7 @@
 private const val SCROLL_DELAY = 100L
 private const val RUBBERBAND_FACTOR = 0.2f
 private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
+private const val TAG = "MediaCarouselScrollHandler"
 
 /**
  * Default spring configuration to use for animations where stiffness and/or damping ratio were not
@@ -63,6 +65,9 @@
     private val logSmartspaceImpression: (Boolean) -> Unit,
     private val logger: MediaUiEventLogger
 ) {
+    /** Trace state logger for media carousel visibility */
+    private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
+
     /** Is the view in RTL */
     val isRtl: Boolean
         get() = scrollView.isLayoutRtl
@@ -182,6 +187,7 @@
             if (field != value) {
                 field = value
                 seekBarUpdateListener.invoke(field)
+                visibleStateLogger.log("$visibleToUser")
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index c33ab12..523414c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -586,7 +586,7 @@
         coroutineScope.launch {
             communalInteractor.isCommunalShowing.collect { value ->
                 isCommunalShowing = value
-                updateDesiredLocation()
+                updateDesiredLocation(forceNoAnimation = true)
             }
         }
     }
@@ -1149,17 +1149,13 @@
             when {
                 mediaFlags.isSceneContainerEnabled() -> desiredLocation
                 dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
-
-                // UMO should show in communal unless the shade is expanding or visible.
-                isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
                 (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
                 qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
                 onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
                 onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
-
-                // Communal does not have its own StatusBarState so it should always have higher
-                // priority for the UMO over the lockscreen.
-                isCommunalShowing -> LOCATION_COMMUNAL_HUB
+                // TODO(b/311234666): revisit logic once interactions between the hub and
+                //  shade/keyguard state are finalized
+                isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
                 onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
                 else -> LOCATION_QQS
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 16dfc21..47df021 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -32,6 +32,7 @@
 import android.util.MathUtils.lerpInvSat
 import androidx.annotation.VisibleForTesting
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.traceSection
 import com.android.internal.graphics.ColorUtils
 import kotlin.math.abs
 import kotlin.math.cos
@@ -127,6 +128,10 @@
         }
 
     override fun draw(canvas: Canvas) {
+        traceSection("SquigglyProgress#draw") { drawTraced(canvas) }
+    }
+
+    private fun drawTraced(canvas: Canvas) {
         if (animate) {
             invalidateSelf()
             val now = SystemClock.uptimeMillis()
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7..afe6285 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.mediaprojection
 
+import android.compat.annotation.ChangeId
+import android.compat.annotation.Disabled
+import android.compat.annotation.Overridable
 import android.content.Context
 import android.media.projection.IMediaProjection
 import android.media.projection.IMediaProjectionManager
@@ -31,6 +34,18 @@
  */
 class MediaProjectionServiceHelper {
     companion object {
+        /**
+         * This change id ensures that users are presented with a choice of capturing a single app
+         * or the entire screen when initiating a MediaProjection session, overriding the usage of
+         * MediaProjectionConfig#createConfigForDefaultDisplay.
+         *
+         * @hide
+         */
+        @ChangeId
+        @Overridable
+        @Disabled
+        const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
+
         private const val TAG = "MediaProjectionServiceHelper"
         private val service =
             IMediaProjectionManager.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..0769731 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,21 +16,26 @@
 
 package com.android.systemui.mediaprojection.permission;
 
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
 import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
 import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
 import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
 import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AlertDialog;
 import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -104,6 +109,7 @@
     }
 
     @Override
+    @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -231,6 +237,9 @@
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
+            final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
+                    OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+                    mPackageName, getHostUserHandle());
             MediaProjectionPermissionDialogDelegate delegate =
                     new MediaProjectionPermissionDialogDelegate(
                             dialogContext,
@@ -242,6 +251,7 @@
                             },
                             () -> finish(RECORD_CANCEL, /* projection= */ null),
                             appName,
+                            overrideDisableSingleAppOption,
                             mUid,
                             mMediaProjectionMetricsLogger);
             mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e93..9ce8070 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@
     private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
     private val onCancelClicked: Runnable,
     private val appName: String?,
+    forceShowPartialScreenshare: Boolean,
     hostUid: Int,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
     BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
-        createOptionList(context, appName, mediaProjectionConfig),
+        createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
         appName,
         hostUid,
         mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@
         private fun createOptionList(
             context: Context,
             appName: String?,
-            mediaProjectionConfig: MediaProjectionConfig?
+            mediaProjectionConfig: MediaProjectionConfig?,
+            overrideDisableSingleAppOption: Boolean = false,
         ): List<ScreenShareOption> {
             val singleAppWarningText =
                 if (appName == null) {
@@ -80,8 +82,13 @@
                     R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                 }
 
+            // The single app option should only be disabled if there is an app name provided,
+            // the client has setup a MediaProjection with
+            // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
+            // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
             val singleAppOptionDisabled =
                 appName != null &&
+                    !overrideDisableSingleAppOption &&
                     mediaProjectionConfig?.regionToCapture ==
                         MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
new file mode 100644
index 0000000..408acf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.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.navigationbar
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavBarButtonClickLog
+import javax.inject.Inject
+
+class NavBarButtonClickLogger
+@Inject
+constructor(@NavBarButtonClickLog private val buffer: LogBuffer) {
+    fun logHomeButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Home Button Triggered" })
+    }
+
+    fun logBackButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Back Button Triggered" })
+    }
+
+    fun logRecentsButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Recents Button Triggered" })
+    }
+
+    fun logImeSwitcherClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Ime Switcher Triggered" })
+    }
+
+    fun logAccessibilityButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Accessibility Button Triggered" })
+    }
+}
+
+private const val TAG = "NavBarButtonClick"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 7d13397..dfe41eb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -56,7 +56,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.systemui.Dumpable;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -381,7 +380,7 @@
             // permission
             final List<String> a11yButtonTargets =
                     mAccessibilityManager.getAccessibilityShortcutTargets(
-                            ShortcutConstants.UserShortcutType.SOFTWARE);
+                            AccessibilityManager.ACCESSIBILITY_BUTTON);
             final int requestingServices = a11yButtonTargets.size();
 
             clickable = requestingServices >= 1;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 068e5fd..95b75ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -84,11 +84,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
 import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.SurfaceChangedCallback;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -285,6 +281,7 @@
     private boolean mImeVisible;
     private final Rect mSamplingBounds = new Rect();
     private final Binder mInsetsSourceOwner = new Binder();
+    private final NavBarButtonClickLogger mNavBarButtonClickLogger;
 
     /**
      * When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -559,7 +556,8 @@
             UserContextProvider userContextProvider,
             WakefulnessLifecycle wakefulnessLifecycle,
             TaskStackChangeListeners taskStackChangeListeners,
-            DisplayTracker displayTracker) {
+            DisplayTracker displayTracker,
+            NavBarButtonClickLogger navBarButtonClickLogger) {
         super(navigationBarView);
         mFrame = navigationBarFrame;
         mContext = context;
@@ -601,6 +599,7 @@
         mTaskStackChangeListeners = taskStackChangeListeners;
         mDisplayTracker = displayTracker;
         mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
+        mNavBarButtonClickLogger = navBarButtonClickLogger;
 
         mNavColorSampleMargin = getResources()
                 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -1276,6 +1275,10 @@
 
         ButtonDispatcher homeButton = mView.getHomeButton();
         homeButton.setOnTouchListener(this::onHomeTouch);
+        homeButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+
+        ButtonDispatcher backButton = mView.getBackButton();
+        backButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
 
         reconfigureHomeLongClick();
 
@@ -1388,6 +1391,8 @@
     }
 
     private void onRecentsClick(View v) {
+        mNavBarButtonClickLogger.logRecentsButtonClick();
+
         if (LatencyTracker.isEnabled(mContext)) {
             LatencyTracker.getInstance(mContext).onActionStart(
                     LatencyTracker.ACTION_TOGGLE_RECENTS);
@@ -1397,6 +1402,7 @@
     }
 
     private void onImeSwitcherClick(View v) {
+        mNavBarButtonClickLogger.logImeSwitcherClick();
         mInputMethodManager.showInputMethodPickerFromSystem(
                 true /* showAuxiliarySubtypes */, mDisplayId);
         mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
@@ -1486,6 +1492,7 @@
     }
 
     private void onAccessibilityClick(View v) {
+        mNavBarButtonClickLogger.logAccessibilityButtonClick();
         final Display display = v.getDisplay();
         mAccessibilityManager.notifyAccessibilityButtonClicked(
                 display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 5739abc..fc37b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -23,6 +23,9 @@
 import android.animation.ValueAnimator;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
 
 import java.util.ArrayList;
 
@@ -52,6 +55,7 @@
     private boolean mVertical;
     private ValueAnimator mFadeAnimator;
     private AccessibilityDelegate mAccessibilityDelegate;
+    private NavBarButtonClickLogger mNavBarButtonClickLogger;
 
     private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
             setAlpha(
@@ -341,4 +345,36 @@
      */
     public void onDestroy() {
     }
+
+    /**
+     * Sets the NavBarButtonClickLogger for all the KeyButtonViews respectively.
+     */
+    public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+        if (navBarButtonClickLogger != null) {
+            mNavBarButtonClickLogger = navBarButtonClickLogger;
+            final int size = mViews.size();
+            for (int i = 0; i < size; i++) {
+                final View v = mViews.get(i);
+                setNavBarButtonClickLoggerForViewChildren(v);
+            }
+        }
+    }
+
+    /**
+     * Recursively explores view hierarchy until the children of provided view are of type
+     * KeyButtonView, so the NavBarButtonClickLogger can be set on them.
+     */
+    private void setNavBarButtonClickLoggerForViewChildren(View v) {
+        if (v instanceof KeyButtonView) {
+            ((KeyButtonView) v).setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+            return;
+        }
+
+        if (v instanceof ViewGroup viewGroup) {
+            final int childrenCount = viewGroup.getChildCount();
+            for (int i = 0; i < childrenCount; i++) {
+                setNavBarButtonClickLoggerForViewChildren(viewGroup.getChildAt(i));
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index df6843d..dbe87ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -59,6 +59,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.navigationbar.KeyButtonRipple;
@@ -86,6 +87,7 @@
     private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private float mDarkIntensity;
     private boolean mHasOvalBg = false;
+    private NavBarButtonClickLogger mNavBarButtonClickLogger;
 
     @VisibleForTesting
     public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
@@ -197,6 +199,10 @@
         mOnClickListener = onClickListener;
     }
 
+    public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+        mNavBarButtonClickLogger = navBarButtonClickLogger;
+    }
+
     public void loadAsync(Icon icon) {
         new AsyncTask<Icon, Void, Drawable>() {
             @Override
@@ -389,11 +395,19 @@
                 uiEvent = longPressSet
                         ? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
                         : NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+
+                if (mNavBarButtonClickLogger != null) {
+                    mNavBarButtonClickLogger.logBackButtonClick();
+                }
                 break;
             case KeyEvent.KEYCODE_HOME:
                 uiEvent = longPressSet
                         ? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
                         : NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+
+                if (mNavBarButtonClickLogger != null) {
+                    mNavBarButtonClickLogger.logHomeButtonClick();
+                }
                 break;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 uiEvent = longPressSet
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index dc39c97..93021ba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -27,7 +27,7 @@
 import android.service.quicksettings.TileService
 import androidx.annotation.GuardedBy
 import com.android.systemui.common.data.repository.PackageChangeRepository
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 53f287b..720120b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -47,6 +47,8 @@
 
     public static final long QS_ANIM_LENGTH = 350;
 
+    private static final long ICON_APPLIED_TRANSACTION_ID = -1;
+
     protected final View mIcon;
     protected int mIconSizePx;
     private boolean mAnimationEnabled = true;
@@ -57,7 +59,8 @@
     @VisibleForTesting
     QSTile.Icon mLastIcon;
 
-    private boolean mIconChangeScheduled;
+    private long mScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID;
+    private long mHighestScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID;
 
     private ValueAnimator mColorAnimator = new ValueAnimator();
 
@@ -117,7 +120,7 @@
     }
 
     protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
-        mIconChangeScheduled = false;
+        mScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID;
         final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
         if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) {
             boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
@@ -173,9 +176,10 @@
             mState = state.state;
             mDisabledByPolicy = state.disabledByPolicy;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
-                mIconChangeScheduled = true;
+                final long iconTransactionId = getNextIconTransactionId();
+                mScheduledIconChangeTransactionId = iconTransactionId;
                 animateGrayScale(mTint, color, iv, () -> {
-                    if (mIconChangeScheduled) {
+                    if (mScheduledIconChangeTransactionId == iconTransactionId) {
                         updateIcon(iv, state, allowAnimations);
                     }
                 });
@@ -237,6 +241,11 @@
         child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
     }
 
+    private long getNextIconTransactionId() {
+        mHighestScheduledIconChangeTransactionId++;
+        return mHighestScheduledIconChangeTransactionId;
+    }
+
     /**
      * Color to tint the tile icon based on state
      */
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 88863cb..a474868 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.recordissue.IssueRecordingService
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingService
@@ -107,7 +108,7 @@
         PendingIntent.getService(
                 userContextProvider.userContext,
                 RecordingService.REQUEST_CODE,
-                RecordingService.getStopIntent(userContextProvider.userContext),
+                IssueRecordingService.getStopIntent(userContextProvider.userContext),
                 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
             )
             .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
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 1b3b584..58e7613 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
@@ -40,7 +40,7 @@
             val icon =
                 Icon.Loaded(
                     resources.getDrawable(
-                        if (data.isEnabled) {
+                        if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
                             R.drawable.qs_flashlight_icon_on
                         } else {
                             R.drawable.qs_flashlight_icon_off
@@ -51,17 +51,22 @@
                 )
             this.icon = { icon }
 
-            if (data.isEnabled) {
+            contentDescription = label
+
+            if (data is FlashlightTileModel.FlashlightTemporarilyUnavailable) {
+                activationState = QSTileState.ActivationState.UNAVAILABLE
+                secondaryLabel =
+                    resources.getString(R.string.quick_settings_flashlight_camera_in_use)
+                stateDescription = secondaryLabel
+                supportedActions = setOf()
+                return@build
+            } else if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
             } else {
                 activationState = QSTileState.ActivationState.INACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
             }
-            contentDescription = label
-            supportedActions =
-                setOf(
-                    QSTileState.UserAction.CLICK,
-                )
+            supportedActions = setOf(QSTileState.UserAction.CLICK)
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
index 53d4cf9..1544804 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
@@ -38,25 +38,30 @@
         user: UserHandle,
         triggers: Flow<DataUpdateTrigger>
     ): Flow<FlashlightTileModel> = conflatedCallbackFlow {
-        val initialValue = flashlightController.isEnabled
-        trySend(FlashlightTileModel(initialValue))
-
         val callback =
             object : FlashlightController.FlashlightListener {
                 override fun onFlashlightChanged(enabled: Boolean) {
-                    trySend(FlashlightTileModel(enabled))
+                    trySend(FlashlightTileModel.FlashlightAvailable(enabled))
                 }
                 override fun onFlashlightError() {
-                    trySend(FlashlightTileModel(false))
+                    trySend(FlashlightTileModel.FlashlightAvailable(false))
                 }
                 override fun onFlashlightAvailabilityChanged(available: Boolean) {
-                    trySend(FlashlightTileModel(flashlightController.isEnabled))
+                    trySend(
+                        if (available)
+                            FlashlightTileModel.FlashlightAvailable(flashlightController.isEnabled)
+                        else FlashlightTileModel.FlashlightTemporarilyUnavailable
+                    )
                 }
             }
         flashlightController.addCallback(callback)
         awaitClose { flashlightController.removeCallback(callback) }
     }
 
+    /**
+     * Used to determine if the tile should be displayed. Not to be confused with the availability
+     * in the data model above. This flow signals whether the tile should be shown or hidden.
+     */
     override fun availability(user: UserHandle): Flow<Boolean> =
         flowOf(flashlightController.hasFlashlight())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
index 9180e30..bedd65e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
@@ -35,7 +35,10 @@
         with(input) {
             when (action) {
                 is QSTileUserAction.Click -> {
-                    if (!ActivityManager.isUserAMonkey()) {
+                    if (
+                        !ActivityManager.isUserAMonkey() &&
+                            input.data is FlashlightTileModel.FlashlightAvailable
+                    ) {
                         flashlightController.setFlashlight(!input.data.isEnabled)
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
index ef6b2be..f54b371 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
@@ -16,9 +16,14 @@
 
 package com.android.systemui.qs.tiles.impl.flashlight.domain.model
 
-/**
- * Flashlight tile model.
- *
- * @param isEnabled is true when the falshlight is enabled;
- */
-@JvmInline value class FlashlightTileModel(val isEnabled: Boolean)
+sealed interface FlashlightTileModel {
+    /**
+     * In this state, the tile can be turned on or off.
+     *
+     * @param isEnabled is true when the flashlight is on and false when off.
+     */
+    @JvmInline value class FlashlightAvailable(val isEnabled: Boolean) : FlashlightTileModel
+
+    /** In this state the tile is grayed out and flashlight cannot be turned on. */
+    data object FlashlightTemporarilyUnavailable : FlashlightTileModel
+}
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
new file mode 100644
index 0000000..26069c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+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 javax.inject.Inject
+
+/** Maps [FontScalingTileModel] to [QSTileState]. */
+class FontScalingTileMapper
+@Inject
+constructor(
+    @Main 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) {
+            val icon =
+                Icon.Loaded(
+                    resources.getDrawable(
+                        R.drawable.ic_qs_font_scaling,
+                        theme,
+                    ),
+                    contentDescription = null
+                )
+            this.icon = { icon }
+            contentDescription = label
+            activationState = QSTileState.ActivationState.ACTIVE
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
new file mode 100644
index 0000000..745e6a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.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.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+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.fontscaling.domain.model.FontScalingTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Provides [FontScalingTileModel]. */
+class FontScalingTileDataInteractor @Inject constructor() :
+    QSTileDataInteractor<FontScalingTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<FontScalingTileModel> = flowOf(FontScalingTileModel)
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
new file mode 100644
index 0000000..b6f4afb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+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.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles font scaling tile clicks. */
+class FontScalingTileUserActionInteractor
+@Inject
+constructor(
+    @Main private val coroutineContext: CoroutineContext,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
+    private val keyguardStateController: KeyguardStateController,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<FontScalingTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<FontScalingTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    // We animate from the touched view only if we are not on the keyguard
+                    val animateFromView: Boolean =
+                        action.view != null && !keyguardStateController.isShowing
+                    val runnable = Runnable {
+                        val dialog: SystemUIDialog =
+                            fontScalingDialogDelegateProvider.get().createDialog()
+                        if (animateFromView) {
+                            dialogLaunchAnimator.showFromView(
+                                dialog,
+                                action.view!!,
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                    INTERACTION_JANK_TAG
+                                )
+                            )
+                        } else {
+                            dialog.show()
+                        }
+                    }
+
+                    withContext(coroutineContext) {
+                        activityStarter.executeRunnableDismissingKeyguard(
+                            runnable,
+                            /* cancelAction= */ null,
+                            /* dismissShade= */ true,
+                            /* afterKeyguardGone= */ true,
+                            /* deferred= */ false
+                        )
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_TEXT_READING_SETTINGS)
+                    )
+                }
+            }
+        }
+    companion object {
+        private const val INTERACTION_JANK_TAG = "font_scaling"
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
similarity index 64%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
index a368de2..76042df 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.model
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+/** FontScaling tile model. No data needed as the tile just opens a dialog. */
+data object FontScalingTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
new file mode 100644
index 0000000..f487258
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.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.systemui.recordissue
+
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.Handler
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.LongRunning
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.screenrecord.RecordingServiceStrings
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class IssueRecordingService
+@Inject
+constructor(
+    controller: RecordingController,
+    @LongRunning executor: Executor,
+    @Main handler: Handler,
+    uiEventLogger: UiEventLogger,
+    notificationManager: NotificationManager,
+    userContextProvider: UserContextProvider,
+    keyguardDismissUtil: KeyguardDismissUtil
+) :
+    RecordingService(
+        controller,
+        executor,
+        handler,
+        uiEventLogger,
+        notificationManager,
+        userContextProvider,
+        keyguardDismissUtil
+    ) {
+
+    override fun getTag(): String = TAG
+
+    override fun getChannelId(): String = CHANNEL_ID
+
+    override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
+
+    companion object {
+        private const val TAG = "IssueRecordingService"
+        private const val CHANNEL_ID = "issue_record"
+
+        /**
+         * Get an intent to stop the issue recording service.
+         *
+         * @param context Context from the requesting activity
+         * @return
+         */
+        fun getStopIntent(context: Context): Intent =
+            Intent(context, RecordingService::class.java)
+                .setAction(ACTION_STOP)
+                .putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+
+        /**
+         * Get an intent to start the issue recording service.
+         *
+         * @param context Context from the requesting activity
+         */
+        fun getStartIntent(context: Context): Intent =
+            Intent(context, RecordingService::class.java).setAction(ACTION_START)
+    }
+}
+
+private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) {
+    override val title
+        get() = res.getString(R.string.issuerecord_title)
+    override val notificationChannelDescription
+        get() = res.getString(R.string.issuerecord_channel_description)
+    override val startErrorResId
+        get() = R.string.issuerecord_start_error
+    override val startError
+        get() = res.getString(R.string.issuerecord_start_error)
+    override val saveErrorResId
+        get() = R.string.issuerecord_save_error
+    override val saveError
+        get() = res.getString(R.string.issuerecord_save_error)
+    override val ongoingRecording
+        get() = res.getString(R.string.issuerecord_ongoing_screen_only)
+    override val backgroundProcessingLabel
+        get() = res.getString(R.string.issuerecord_background_processing_label)
+    override val saveTitle
+        get() = res.getString(R.string.issuerecord_save_title)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index e051df4..80f11f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.recordissue
 
 import android.annotation.SuppressLint
-import android.app.Activity
 import android.app.BroadcastOptions
 import android.app.PendingIntent
 import android.content.Context
@@ -45,7 +44,6 @@
 import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.screenrecord.ScreenRecordingAudioSource
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -183,13 +181,7 @@
         PendingIntent.getForegroundService(
                 userContextProvider.userContext,
                 RecordingService.REQUEST_CODE,
-                RecordingService.getStartIntent(
-                    userContextProvider.userContext,
-                    Activity.RESULT_OK,
-                    ScreenRecordingAudioSource.NONE.ordinal,
-                    false,
-                    null
-                ),
+                IssueRecordingService.getStartIntent(userContextProvider.userContext),
                 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
             )
             .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 246ccb1..dcd87c0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.scene.domain.startable
 
+import android.app.StatusBarManager
 import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -42,6 +43,8 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printSection
 import com.android.systemui.util.println
@@ -51,10 +54,12 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
@@ -81,6 +86,8 @@
     private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
     private val authenticationInteractor: Lazy<AuthenticationInteractor>,
     private val windowController: NotificationShadeWindowController,
+    private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+    private val centralSurfaces: CentralSurfaces,
 ) : CoreStartable {
 
     override fun start() {
@@ -91,6 +98,7 @@
             hydrateSystemUiState()
             collectFalsingSignals()
             hydrateWindowFocus()
+            hydrateInteractionState()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -112,26 +120,39 @@
     private fun hydrateVisibility() {
         applicationScope.launch {
             // TODO(b/296114544): Combine with some global hun state to make it visible!
-            sceneInteractor.transitionState
-                .mapNotNull { state ->
-                    when (state) {
-                        is ObservableTransitionState.Idle -> {
-                            if (state.scene != SceneKey.Gone) {
-                                true to "scene is not Gone"
-                            } else {
-                                false to "scene is Gone"
-                            }
-                        }
-                        is ObservableTransitionState.Transition -> {
-                            if (state.fromScene == SceneKey.Gone) {
-                                true to "scene transitioning away from Gone"
-                            } else {
-                                null
-                            }
-                        }
-                    }
+            combine(
+                    deviceProvisioningInteractor.isDeviceProvisioned,
+                    deviceProvisioningInteractor.isFactoryResetProtectionActive,
+                ) { isDeviceProvisioned, isFrpActive ->
+                    isDeviceProvisioned && !isFrpActive
                 }
                 .distinctUntilChanged()
+                .flatMapLatest { isAllowedToBeVisible ->
+                    if (isAllowedToBeVisible) {
+                        sceneInteractor.transitionState
+                            .mapNotNull { state ->
+                                when (state) {
+                                    is ObservableTransitionState.Idle -> {
+                                        if (state.scene != SceneKey.Gone) {
+                                            true to "scene is not Gone"
+                                        } else {
+                                            false to "scene is Gone"
+                                        }
+                                    }
+                                    is ObservableTransitionState.Transition -> {
+                                        if (state.fromScene == SceneKey.Gone) {
+                                            true to "scene transitioning away from Gone"
+                                        } else {
+                                            null
+                                        }
+                                    }
+                                }
+                            }
+                            .distinctUntilChanged()
+                    } else {
+                        flowOf(false to "Device not provisioned or Factory Reset Protection active")
+                    }
+                }
                 .collect { (isVisible, loggingReason) ->
                     sceneInteractor.setVisible(isVisible, loggingReason)
                 }
@@ -359,6 +380,46 @@
         }
     }
 
+    /** Keeps the interaction state of [CentralSurfaces] up-to-date. */
+    private fun hydrateInteractionState() {
+        applicationScope.launch {
+            deviceEntryInteractor.isUnlocked
+                .map { !it }
+                .flatMapLatest { isDeviceLocked ->
+                    if (isDeviceLocked) {
+                        sceneInteractor.transitionState
+                            .mapNotNull { it as? ObservableTransitionState.Idle }
+                            .map { it.scene }
+                            .distinctUntilChanged()
+                            .map { sceneKey ->
+                                when (sceneKey) {
+                                    // When locked, showing the lockscreen scene should be reported
+                                    // as "interacting" while showing other scenes should report as
+                                    // "not interacting".
+                                    //
+                                    // This is done here in order to match the legacy
+                                    // implementation. The real reason why is lost to lore and myth.
+                                    SceneKey.Lockscreen -> true
+                                    SceneKey.Bouncer -> false
+                                    SceneKey.Shade -> false
+                                    else -> null
+                                }
+                            }
+                    } else {
+                        flowOf(null)
+                    }
+                }
+                .collect { isInteractingOrNull ->
+                    isInteractingOrNull?.let { isInteracting ->
+                        centralSurfaces.setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            isInteracting,
+                        )
+                    }
+                }
+        }
+    }
+
     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
         sceneInteractor.changeScene(
             scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b5a1313..b355d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -24,7 +24,6 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.media.MediaRecorder;
 import android.net.Uri;
@@ -71,8 +70,8 @@
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
     private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
 
-    private static final String ACTION_START = "com.android.systemui.screenrecord.START";
-    private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
+    protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+    protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
     private static final String ACTION_STOP_NOTIF =
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
     private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
@@ -90,6 +89,7 @@
     private final NotificationManager mNotificationManager;
     private final UserContextProvider mUserContextTracker;
     private int mNotificationId = NOTIF_BASE_ID;
+    private RecordingServiceStrings mStrings;
 
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
@@ -134,9 +134,9 @@
             return Service.START_NOT_STICKY;
         }
         String action = intent.getAction();
-        Log.d(TAG, "onStartCommand " + action);
+        Log.d(getTag(), "onStartCommand " + action);
         NotificationChannel channel = new NotificationChannel(
-                CHANNEL_ID,
+                getChannelId(),
                 getString(R.string.screenrecord_title),
                 NotificationManager.IMPORTANCE_DEFAULT);
         channel.setDescription(getString(R.string.screenrecord_channel_description));
@@ -152,7 +152,7 @@
                 mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();
                 mAudioSource = ScreenRecordingAudioSource
                         .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
-                Log.d(TAG, "recording with audio source " + mAudioSource);
+                Log.d(getTag(), "recording with audio source " + mAudioSource);
                 mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
                 MediaProjectionCaptureTarget captureTarget =
                         intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
@@ -207,7 +207,7 @@
                         .setType("video/mp4")
                         .putExtra(Intent.EXTRA_STREAM, shareUri);
                 mKeyguardDismissUtil.executeWhenUnlocked(() -> {
-                    String shareLabel = getResources().getString(R.string.screenrecord_share_label);
+                    String shareLabel = strings().getShareLabel();
                     startActivity(Intent.createChooser(shareIntent, shareLabel)
                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                     // Remove notification
@@ -270,13 +270,11 @@
      */
     @VisibleForTesting
     protected void createErrorNotification() {
-        Resources res = getResources();
         Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                res.getString(R.string.screenrecord_title));
-        String notificationTitle = res.getString(R.string.screenrecord_start_error);
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
+        String notificationTitle = strings().getStartError();
 
-        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+        Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(notificationTitle)
                 .addExtras(extras);
@@ -290,14 +288,12 @@
 
     @VisibleForTesting
     protected void createRecordingNotification() {
-        Resources res = getResources();
         Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                res.getString(R.string.screenrecord_title));
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
 
         String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
-                ? res.getString(R.string.screenrecord_ongoing_screen_only)
-                : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+                ? strings().getOngoingRecording()
+                : strings().getOngoingRecordingWithAudio();
 
         PendingIntent pendingIntent = PendingIntent.getService(
                 this,
@@ -306,9 +302,9 @@
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         Notification.Action stopAction = new Notification.Action.Builder(
                 Icon.createWithResource(this, R.drawable.ic_android),
-                getResources().getString(R.string.screenrecord_stop_label),
+                strings().getStopLabel(),
                 pendingIntent).build();
-        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+        Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(notificationTitle)
                 .setUsesChronometer(true)
@@ -323,19 +319,17 @@
 
     @VisibleForTesting
     protected Notification createProcessingNotification() {
-        Resources res = getApplicationContext().getResources();
         String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
-                ? res.getString(R.string.screenrecord_ongoing_screen_only)
-                : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+                ? strings().getOngoingRecording()
+                : strings().getOngoingRecordingWithAudio();
 
         Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                res.getString(R.string.screenrecord_title));
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
 
-        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+        Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setContentTitle(notificationTitle)
                 .setContentText(
-                        getResources().getString(R.string.screenrecord_background_processing_label))
+                        strings().getBackgroundProcessingLabel())
                 .setSmallIcon(R.drawable.ic_screenrecord)
                 .setGroup(GROUP_KEY)
                 .addExtras(extras);
@@ -351,7 +345,7 @@
 
         Notification.Action shareAction = new Notification.Action.Builder(
                 Icon.createWithResource(this, R.drawable.ic_screenrecord),
-                getResources().getString(R.string.screenrecord_share_label),
+                strings().getShareLabel(),
                 PendingIntent.getService(
                         this,
                         REQUEST_CODE,
@@ -360,13 +354,12 @@
                 .build();
 
         Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                getResources().getString(R.string.screenrecord_title));
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
 
-        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+        Notification.Builder builder = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
-                .setContentText(getResources().getString(R.string.screenrecord_save_text))
+                .setContentTitle(strings().getSaveTitle())
+                .setContentText(strings().getSaveText())
                 .setContentIntent(PendingIntent.getActivity(
                         this,
                         REQUEST_CODE,
@@ -394,15 +387,15 @@
     private void postGroupNotification(UserHandle currentUser) {
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                getResources().getString(R.string.screenrecord_title));
-        Notification groupNotif = new Notification.Builder(this, CHANNEL_ID)
+                strings().getTitle());
+        Notification groupNotif = new Notification.Builder(this, getChannelId())
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
+                .setContentTitle(strings().getSaveTitle())
                 .setGroup(GROUP_KEY)
                 .setGroupSummary(true)
                 .setExtras(extras)
                 .build();
-        mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser);
+        mNotificationManager.notifyAsUser(getTag(), NOTIF_BASE_ID, groupNotif, currentUser);
     }
 
     private void stopService() {
@@ -413,7 +406,7 @@
         if (userId == USER_ID_NOT_SPECIFIED) {
             userId = mUserContextTracker.getUserContext().getUserId();
         }
-        Log.d(TAG, "notifying for user " + userId);
+        Log.d(getTag(), "notifying for user " + userId);
         setTapsVisible(mOriginalShowTaps);
         if (getRecorder() != null) {
             try {
@@ -424,7 +417,7 @@
                 // let's release the recorder and delete all temporary files in this case
                 getRecorder().release();
                 showErrorToast(R.string.screenrecord_start_error);
-                Log.e(TAG, "stopRecording called, but there was an error when ending"
+                Log.e(getTag(), "stopRecording called, but there was an error when ending"
                         + "recording");
                 exception.printStackTrace();
                 createErrorNotification();
@@ -435,7 +428,7 @@
                 throw new RuntimeException(throwable);
             }
         } else {
-            Log.e(TAG, "stopRecording called, but recorder was null");
+            Log.e(getTag(), "stopRecording called, but recorder was null");
         }
         updateState(false);
         stopForeground(STOP_FOREGROUND_DETACH);
@@ -449,13 +442,13 @@
 
         mLongExecutor.execute(() -> {
             try {
-                Log.d(TAG, "saving recording");
+                Log.d(getTag(), "saving recording");
                 Notification notification = createSaveNotification(getRecorder().save());
                 postGroupNotification(currentUser);
                 mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
                         currentUser);
             } catch (IOException | IllegalStateException e) {
-                Log.e(TAG, "Error saving screen recording: " + e.getMessage());
+                Log.e(getTag(), "Error saving screen recording: " + e.getMessage());
                 e.printStackTrace();
                 showErrorToast(R.string.screenrecord_save_error);
                 mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
@@ -468,6 +461,26 @@
         Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value);
     }
 
+    protected String getTag() {
+        return TAG;
+    }
+
+    protected String getChannelId() {
+        return CHANNEL_ID;
+    }
+
+    private RecordingServiceStrings strings() {
+        if (mStrings == null) {
+            mStrings = provideRecordingServiceStrings();
+        }
+        return mStrings;
+    }
+
+    protected RecordingServiceStrings provideRecordingServiceStrings() {
+        return new RecordingServiceStrings(getResources());
+    }
+
+
     /**
      * Get an intent to stop the recording service.
      * @param context Context from the requesting activity
@@ -484,25 +497,25 @@
      * @param context
      * @return
      */
-    protected static Intent getNotificationIntent(Context context) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_STOP_NOTIF);
+    protected Intent getNotificationIntent(Context context) {
+        return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
     }
 
-    private static Intent getShareIntent(Context context, String path) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_SHARE)
+    private Intent getShareIntent(Context context, String path) {
+        return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
                 .putExtra(EXTRA_PATH, path);
     }
 
     @Override
     public void onInfo(MediaRecorder mr, int what, int extra) {
-        Log.d(TAG, "Media recorder info: " + what);
+        Log.d(getTag(), "Media recorder info: " + what);
         onStartCommand(getStopIntent(this), 0, 0);
     }
 
     @Override
     public void onStopped() {
         if (mController.isRecording()) {
-            Log.d(TAG, "Stopping recording because the system requested the stop");
+            Log.d(getTag(), "Stopping recording because the system requested the stop");
             stopService();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt
new file mode 100644
index 0000000..fdb1eb6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.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.screenrecord
+
+import android.content.res.Resources
+import com.android.systemui.res.R
+
+open class RecordingServiceStrings(private val res: Resources) {
+    open val title
+        get() = res.getString(R.string.screenrecord_title)
+    open val notificationChannelDescription
+        get() = res.getString(R.string.screenrecord_channel_description)
+    open val startErrorResId
+        get() = R.string.screenrecord_start_error
+    open val startError
+        get() = res.getString(R.string.screenrecord_start_error)
+    open val saveErrorResId
+        get() = R.string.screenrecord_save_error
+    open val saveError
+        get() = res.getString(R.string.screenrecord_save_error)
+    open val ongoingRecording
+        get() = res.getString(R.string.screenrecord_ongoing_screen_only)
+    open val backgroundProcessingLabel
+        get() = res.getString(R.string.screenrecord_background_processing_label)
+    open val saveTitle
+        get() = res.getString(R.string.screenrecord_save_title)
+
+    val saveText
+        get() = res.getString(R.string.screenrecord_save_text)
+    val ongoingRecordingWithAudio
+        get() = res.getString(R.string.screenrecord_ongoing_screen_and_audio)
+    val stopLabel
+        get() = res.getString(R.string.screenrecord_stop_label)
+    val shareLabel
+        get() = res.getString(R.string.screenrecord_share_label)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
rename to packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index a0dd924..fd807db 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.settings.dagger;
+package com.android.systemui.settings;
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -29,14 +29,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.DisplayTrackerImpl;
-import com.android.systemui.settings.UserContentResolverProvider;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserFileManagerImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.settings.UserTrackerImpl;
 
 import dagger.Binds;
 import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
new file mode 100644
index 0000000..76d1d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object SecureSettingsRepositoryModule {
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    fun provideSecureSettingsRepository(
+        contentResolver: ContentResolver,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+    ): SecureSettingsRepository =
+        SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
deleted file mode 100644
index b09bfe2..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.settings
-
-import android.annotation.UserIdInt
-import android.content.Context
-import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Extension functions for [UserFileManager]. */
-object UserFileManagerExt {
-
-    /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
-    fun UserFileManager.observeSharedPreferences(
-        fileName: String,
-        @Context.PreferencesMode mode: Int,
-        @UserIdInt userId: Int
-    ): Flow<Unit> = conflatedCallbackFlow {
-        val sharedPrefs = getSharedPreferences(fileName, mode, userId)
-
-        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
-
-        sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
-        awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index be1fa2b..e9af295 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,11 +30,11 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.ViewController;
@@ -236,7 +236,7 @@
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
             mTracking = true;
-            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
+            mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), false);
                 SeekableSliderEventProducer eventProducer =
@@ -255,7 +255,7 @@
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
             mTracking = false;
-            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
+            mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), true);
                 SeekableSliderEventProducer eventProducer =
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
index 3a30880..a8641bf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -21,10 +21,10 @@
 
 public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
 
-    @UiEvent(doc = "slider started to track touch")
-    SLIDER_STARTED_TRACKING_TOUCH(1472),
-    @UiEvent(doc = "slider stopped tracking touch")
-    SLIDER_STOPPED_TRACKING_TOUCH(1473);
+    @UiEvent(doc = "brightness slider started to track touch")
+    BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH(1472),
+    @UiEvent(doc = "brightness slider stopped tracking touch")
+    BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH(1473);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 09e4e75..133fa12 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -165,6 +165,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.shared.model.WakefulnessModel;
 import com.android.systemui.res.R;
+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.transition.ShadeTransitionController;
@@ -1020,22 +1021,24 @@
                 instantCollapse();
             } else {
                 mView.animate().cancel();
-                mView.animate()
-                        .alpha(0f)
-                        .setStartDelay(0)
-                        // Translate up by 4%.
-                        .translationY(mView.getHeight() * -0.04f)
-                        // This start delay is to give us time to animate out before
-                        // the launcher icons animation starts, so use that as our
-                        // duration.
-                        .setDuration(unlockAnimationStartDelay)
-                        .setInterpolator(EMPHASIZED_ACCELERATE)
-                        .withEndAction(() -> {
-                            instantCollapse();
-                            mView.setAlpha(1f);
-                            mView.setTranslationY(0f);
-                        })
-                        .start();
+                if (!KeyguardShadeMigrationNssl.isEnabled()) {
+                    mView.animate()
+                            .alpha(0f)
+                            .setStartDelay(0)
+                            // Translate up by 4%.
+                            .translationY(mView.getHeight() * -0.04f)
+                            // This start delay is to give us time to animate out before
+                            // the launcher icons animation starts, so use that as our
+                            // duration.
+                            .setDuration(unlockAnimationStartDelay)
+                            .setInterpolator(EMPHASIZED_ACCELERATE)
+                            .withEndAction(() -> {
+                                instantCollapse();
+                                mView.setAlpha(1f);
+                                mView.setTranslationY(0f);
+                            })
+                            .start();
+                }
             }
         }
     }
@@ -2083,6 +2086,7 @@
         mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
         setClosingWithAlphaFadeout(!expand && !isKeyguardShowing() && getFadeoutAlpha() == 1.0f);
         mNotificationStackScrollLayoutController.setPanelFlinging(true);
+        mShadeRepository.setCurrentFling(new FlingInfo(expand, vel));
         if (target == mExpandedHeight && mOverExpansion == 0.0f) {
             // We're at the target and didn't fling and there's no overshoot
             onFlingEnd(false /* cancelled */);
@@ -2199,6 +2203,7 @@
         mShadeLog.d("onFlingEnd called"); // TODO(b/277909752): remove log when bug is fixed
         // expandImmediate should be always reset at the end of animation
         mQsController.setExpandImmediate(false);
+        mShadeRepository.setCurrentFling(null);
     }
 
     private boolean isInContentBounds(float x, float y) {
@@ -2472,11 +2477,9 @@
             return 0;
         }
         if (!mKeyguardBypassController.getBypassEnabled()) {
-            if (migrateClocksToBlueprint()) {
-                View nsslPlaceholder = mView.getRootView().findViewById(R.id.nssl_placeholder);
-                if (!mSplitShadeEnabled && nsslPlaceholder != null) {
-                    return nsslPlaceholder.getTop();
-                }
+            if (migrateClocksToBlueprint() && !mSplitShadeEnabled) {
+                return (int) mKeyguardInteractor.getNotificationContainerBounds()
+                        .getValue().getTop();
             }
 
             return mClockPositionResult.stackScrollerPadding;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0053474..f7fed53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -183,7 +183,8 @@
         mBackgroundExecutor = backgroundExecutor;
         mColorExtractor = colorExtractor;
         mScreenOffAnimationController = screenOffAnimationController;
-        dumpManager.registerDumpable(this);
+        // prefix with {slow} to make sure this dumps at the END of the critical section.
+        dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
         mAuthController = authController;
         mUserInteractor = userInteractor;
         mSceneContainerFlags = sceneContainerFlags;
@@ -331,8 +332,8 @@
         );
         collectFlow(
                 mWindowRootView,
-                mCommunalInteractor.get().isCommunalShowing(),
-                this::onCommunalShowingChanged
+                mCommunalInteractor.get().isCommunalVisible(),
+                this::onCommunalVisibleChanged
         );
     }
 
@@ -475,6 +476,9 @@
             }
             visible = true;
             mLogger.d("Visibility forced to be true");
+        } else if (state.communalVisible) {
+            visible = true;
+            mLogger.d("Visibility forced to be true by communal");
         }
         if (mWindowRootView != null) {
             if (visible) {
@@ -510,15 +514,15 @@
     }
 
     private void applyUserActivityTimeout(NotificationShadeWindowState state) {
-        final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+        final Boolean communalVisible = state.isCommunalVisibleAndNotOccluded();
         final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
         long timeout = -1;
-        if ((communalShowing || keyguardShowing)
+        if ((communalVisible || keyguardShowing)
                 && state.statusBarState == StatusBarState.KEYGUARD
                 && !state.qsExpanded) {
             if (state.bouncerShowing) {
                 timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
-            } else if (communalShowing) {
+            } else if (communalVisible) {
                 timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
             } else if (keyguardShowing) {
                 timeout = mLockScreenDisplayTimeout;
@@ -624,7 +628,7 @@
                 state.dozing,
                 state.scrimsVisibility,
                 state.backgroundBlurRadius,
-                state.communalShowing
+                state.communalVisible
         );
     }
 
@@ -749,8 +753,8 @@
     }
 
     @VisibleForTesting
-    void onCommunalShowingChanged(Boolean showing) {
-        mCurrentState.communalShowing = showing;
+    void onCommunalVisibleChanged(Boolean visible) {
+        mCurrentState.communalVisible = visible;
         apply(mCurrentState);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index f9c9d83..e0a98b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,15 +58,15 @@
     @JvmField var dreaming: Boolean = false,
     @JvmField var scrimsVisibility: Int = 0,
     @JvmField var backgroundBlurRadius: Int = 0,
-    @JvmField var communalShowing: Boolean = false,
+    @JvmField var communalVisible: Boolean = false,
 ) {
 
     fun isKeyguardShowingAndNotOccluded(): Boolean {
         return keyguardShowing && !keyguardOccluded
     }
 
-    fun isCommunalShowingAndNotOccluded(): Boolean {
-        return communalShowing && !keyguardOccluded
+    fun isCommunalVisibleAndNotOccluded(): Boolean {
+        return communalVisible && !keyguardOccluded
     }
 
     /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
@@ -99,7 +99,7 @@
             dozing.toString(),
             scrimsVisibility.toString(),
             backgroundBlurRadius.toString(),
-            communalShowing.toString(),
+            communalVisible.toString(),
         )
     }
 
@@ -140,7 +140,7 @@
             dozing: Boolean,
             scrimsVisibility: Int,
             backgroundBlurRadius: Int,
-            communalShowing: Boolean,
+            communalVisible: Boolean,
         ) {
             buffer.advance().apply {
                 this.keyguardShowing = keyguardShowing
@@ -172,7 +172,7 @@
                 this.dozing = dozing
                 this.scrimsVisibility = scrimsVisibility
                 this.backgroundBlurRadius = backgroundBlurRadius
-                this.communalShowing = communalShowing
+                this.communalVisible = communalVisible
             }
         }
 
@@ -218,7 +218,7 @@
                 "dozing",
                 "scrimsVisibility",
                 "backgroundBlurRadius",
-                "communalShowing"
+                "communalVisible"
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 84cad1d..c0afa32 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -30,8 +30,6 @@
 import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentService
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,18 +57,17 @@
 
 @SysUISingleton
 class NotificationsQSContainerController @Inject constructor(
-        view: NotificationsQuickSettingsContainer,
-        private val navigationModeController: NavigationModeController,
-        private val overviewProxyService: OverviewProxyService,
-        private val shadeHeaderController: ShadeHeaderController,
-        private val shadeInteractor: ShadeInteractor,
-        private val fragmentService: FragmentService,
-        @Main private val delayableExecutor: DelayableExecutor,
-        private val featureFlags: FeatureFlags,
-        private val
-            notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
-        private val splitShadeStateController: SplitShadeStateController,
-        private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
+    view: NotificationsQuickSettingsContainer,
+    private val navigationModeController: NavigationModeController,
+    private val overviewProxyService: OverviewProxyService,
+    private val shadeHeaderController: ShadeHeaderController,
+    private val shadeInteractor: ShadeInteractor,
+    private val fragmentService: FragmentService,
+    @Main private val delayableExecutor: DelayableExecutor,
+    private val
+    notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+    private val splitShadeStateController: SplitShadeStateController,
+    private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
     private var splitShadeEnabled = false
@@ -133,9 +130,6 @@
         isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
 
         mView.setStackScroller(notificationStackScrollLayoutController.getView())
-        if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
-            mView.enableGraphOptimization()
-        }
     }
 
     public override fun onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index de3d16a..25e558e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,10 +32,10 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.res.R;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 
 import java.util.ArrayList;
@@ -73,6 +73,7 @@
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
+        setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
     }
 
     @Override
@@ -180,10 +181,6 @@
         super.dispatchDraw(canvas);
     }
 
-    void enableGraphOptimization() {
-        setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
-    }
-
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 10b9db0..4e8b403 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.ShadeTouchLog
@@ -34,11 +35,13 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Implementation of ShadeController backed by scenes instead of NPVC.
@@ -50,6 +53,7 @@
 class ShadeControllerSceneImpl
 @Inject
 constructor(
+    @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val scope: CoroutineScope,
     private val shadeInteractor: ShadeInteractor,
     private val sceneInteractor: SceneInteractor,
@@ -193,7 +197,11 @@
     }
 
     override fun setVisibilityListener(listener: ShadeVisibilityListener) {
-        scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+        scope.launch {
+            sceneInteractor.isVisible.collect { isVisible ->
+                withContext(mainDispatcher) { listener.expandedVisibleChanged(isVisible) }
+            }
+        }
     }
 
     @ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 67bb814..f89a9c70 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
 import dagger.Binds
@@ -35,6 +36,10 @@
 
     @Binds
     @SysUISingleton
+    abstract fun bindsShadeBack(sbai: ShadeViewControllerEmptyImpl): ShadeBackActionInteractor
+
+    @Binds
+    @SysUISingleton
     abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index fc2c3ee..e4d5d22 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -78,6 +80,20 @@
                 sceneContainerOff.get()
             }
         }
+
+        @Provides
+        @SysUISingleton
+        fun provideShadeBackActionInteractor(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
+            sceneContainerOff: Provider<NotificationPanelViewController>
+        ): ShadeBackActionInteractor {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 4f970b3..0befb61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shade
 
 import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.GestureRecorder
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -25,7 +26,7 @@
  * this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
  * pulled up into ShadeViewController.
  */
-interface ShadeSurface : ShadeViewController {
+interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor {
     /** Initialize objects instead of injecting to avoid circular dependencies. */
     fun initDependencies(
         centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3430eed..74035bd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -79,15 +79,8 @@
     /** Collapses the shade instantly without animation. */
     fun instantCollapse()
 
-    /**
-     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
-     * in split shade, it will collapse the whole shade.
-     *
-     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
-     */
-    fun animateCollapseQs(fullyCollapse: Boolean)
-
     /** Returns whether the shade can be collapsed. */
+    @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
     fun canBeCollapsed(): Boolean
 
     /** Returns whether the shade is in the process of collapsing. */
@@ -142,22 +135,6 @@
     /** Sets the amount of progress in the status bar launch animation. */
     fun applyLaunchAnimationProgress(linearProgress: Float)
 
-    /**
-     * Close the keyguard user switcher if it is open and capable of closing.
-     *
-     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
-     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
-     *
-     * @return true if the keyguard user switcher was open, and is now closed
-     */
-    fun closeUserSwitcherIfOpen(): Boolean
-
-    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
-    fun onBackPressed()
-
-    /** Sets progress of the predictive back animation. */
-    fun onBackProgressed(progressFraction: Float)
-
     /** Sets the alpha value of the shade to a value between 0 and 255. */
     fun setAlpha(alpha: Int, animate: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 1240c6e..5d966ac 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,13 +19,15 @@
 import android.view.MotionEvent
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
 import java.util.function.Consumer
 import javax.inject.Inject
 
 /** Empty implementation of ShadeViewController for variants with no shade. */
-class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+class ShadeViewControllerEmptyImpl @Inject constructor() :
+    ShadeViewController, ShadeBackActionInteractor {
     override fun expand(animate: Boolean) {}
     override fun expandToQs() {}
     override fun expandToNotifications() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.kt
new file mode 100644
index 0000000..d7f96e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FlingInfo.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.shade.data.repository
+
+/**
+ * Information about a fling on the shade: whether we're flinging expanded or collapsed, and the
+ * velocity of the touch gesture that started the fling (if applicable).
+ */
+data class FlingInfo(
+    val expand: Boolean,
+    val velocity: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 2445bdb..e5ff977 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -29,6 +29,15 @@
      */
     val qsExpansion: StateFlow<Float>
 
+    /** Amount shade has expanded with regard to the UDFPS location */
+    val udfpsTransitionToFullShadeProgress: StateFlow<Float>
+
+    /**
+     * Information about the currently running fling animation, or null if no fling animation is
+     * running.
+     */
+    val currentFling: StateFlow<FlingInfo?>
+
     /**
      * The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed,
      * 1 means fully expanded. Value resets to 0 when the user finishes dragging.
@@ -132,15 +141,18 @@
     @Deprecated("Use ShadeInteractor instead")
     fun setLegacyLockscreenShadeTracking(tracking: Boolean)
 
-    /** Amount shade has expanded with regard to the UDFPS location */
-    val udfpsTransitionToFullShadeProgress: StateFlow<Float>
-
     /** The amount QS has expanded without notifications */
     fun setQsExpansion(qsExpansion: Float)
 
     fun setUdfpsTransitionToFullShadeProgress(progress: Float)
 
     /**
+     * Sets the [FlingInfo] of the currently animating fling. If [info] is null, no fling is
+     * animating.
+     */
+    fun setCurrentFling(info: FlingInfo?)
+
+    /**
      * Set the amount the shade has dragged down by the user, [0-1]. 0 means fully collapsed, 1
      * means fully expanded.
      */
@@ -168,6 +180,9 @@
     override val udfpsTransitionToFullShadeProgress: StateFlow<Float> =
         _udfpsTransitionToFullShadeProgress.asStateFlow()
 
+    private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null)
+    override val currentFling: StateFlow<FlingInfo?> = _currentFling.asStateFlow()
+
     private val _legacyShadeExpansion = MutableStateFlow(0f)
     @Deprecated("Use ShadeInteractor.shadeExpansion instead")
     override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow()
@@ -247,11 +262,6 @@
         _qsExpansion.value = qsExpansion
     }
 
-    @Deprecated("Should only be called by NPVC and tests")
-    override fun setLegacyShadeExpansion(expandedFraction: Float) {
-        _legacyShadeExpansion.value = expandedFraction
-    }
-
     override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
         _lockscreenShadeExpansion.value = lockscreenShadeExpansion
     }
@@ -260,6 +270,15 @@
         _udfpsTransitionToFullShadeProgress.value = progress
     }
 
+    override fun setCurrentFling(info: FlingInfo?) {
+        _currentFling.value = info
+    }
+
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyShadeExpansion(expandedFraction: Float) {
+        _legacyShadeExpansion.value = expandedFraction
+    }
+
     companion object {
         private const val TAG = "ShadeRepository"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
new file mode 100644
index 0000000..15ea219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
@@ -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 com.android.systemui.shade.domain.interactor
+
+/** Allows the back action to interact with the shade. */
+interface ShadeBackActionInteractor {
+    /**
+     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+     * in split shade, it will collapse the whole shade.
+     *
+     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+     */
+    fun animateCollapseQs(fullyCollapse: Boolean)
+
+    /** Returns whether the shade can be collapsed. */
+    fun canBeCollapsed(): Boolean
+
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    @Deprecated("Only supported by very old devices that will not adopt scenes.")
+    fun closeUserSwitcherIfOpen(): Boolean
+
+    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+    fun onBackPressed()
+
+    /** Sets progress of the predictive back animation. */
+    @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+    fun onBackProgressed(progressFraction: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
new file mode 100644
index 0000000..9bbe1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Implementation of ShadeBackActionInteractor backed by scenes. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeBackActionInteractorImpl
+@Inject
+constructor(
+    val shadeInteractor: ShadeInteractor,
+    val sceneInteractor: SceneInteractor,
+    val deviceEntryInteractor: DeviceEntryInteractor,
+) : ShadeBackActionInteractor {
+    override fun animateCollapseQs(fullyCollapse: Boolean) {
+        if (shadeInteractor.isQsExpanded.value) {
+            val key =
+                if (fullyCollapse) {
+                    if (deviceEntryInteractor.isDeviceEntered.value) {
+                        SceneKey.Gone
+                    } else {
+                        SceneKey.Lockscreen
+                    }
+                } else {
+                    SceneKey.Shade
+                }
+            sceneInteractor.changeScene(SceneModel(key), "animateCollapseQs")
+        }
+    }
+
+    override fun canBeCollapsed(): Boolean {
+        return shadeInteractor.isAnyExpanded.value && !shadeInteractor.isUserInteracting.value
+    }
+
+    @Deprecated("Only supported by very old devices that will not adopt scenes.")
+    override fun closeUserSwitcherIfOpen(): Boolean {
+        return false
+    }
+
+    override fun onBackPressed() {
+        animateCollapseQs(false)
+    }
+
+    @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+    override fun onBackProgressed(progressFraction: Float) {
+        // Not supported. Do nothing.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index a71cf95..e619806 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,9 +25,10 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
+import com.android.systemui.util.kotlin.combine
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -44,7 +45,7 @@
 @Inject
 constructor(
     @Application val scope: CoroutineScope,
-    deviceProvisioningRepository: DeviceProvisioningRepository,
+    deviceProvisioningInteractor: DeviceProvisioningInteractor,
     disableFlagsRepository: DisableFlagsRepository,
     dozeParams: DozeParameters,
     keyguardRepository: KeyguardRepository,
@@ -56,7 +57,7 @@
 ) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
     override val isShadeEnabled: StateFlow<Boolean> =
         combine(
-                deviceProvisioningRepository.isFactoryResetProtectionActive,
+                deviceProvisioningInteractor.isFactoryResetProtectionActive,
                 disableFlagsRepository.disableFlags,
             ) { isFrpActive, isDisabledByFlags ->
                 isDisabledByFlags.isShadeEnabled() && !isFrpActive
@@ -83,7 +84,7 @@
             powerInteractor.isAsleep,
             keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
             keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
-            deviceProvisioningRepository.isFactoryResetProtectionActive,
+            deviceProvisioningInteractor.isFactoryResetProtectionActive,
         ) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
             when {
                 // Touches are disabled when Factory Reset Protection is active
@@ -103,7 +104,7 @@
             isShadeEnabled,
             keyguardRepository.isDozing,
             userSetupRepository.isUserSetUp,
-            deviceProvisioningRepository.isDeviceProvisioned,
+            deviceProvisioningInteractor.isDeviceProvisioned,
         ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
             isDeviceProvisioned &&
                 // Disallow QS during setup if it's a simple user switcher. (The user intends to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f8be1c..ef50265 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -14,6 +14,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.ExpandHelper
+import com.android.systemui.Flags.nsslFalsingFix
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
 import com.android.systemui.classifier.Classifier
@@ -889,7 +890,7 @@
                     isDraggingDown = false
                     isTrackpadReverseScroll = false
                     shadeRepository.setLegacyLockscreenShadeTracking(false)
-                    if (KeyguardShadeMigrationNssl.isEnabled) {
+                    if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled) {
                         return true
                     }
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 32cd56c..b64e0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -56,7 +56,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -73,7 +72,7 @@
  * their own version of CentralSurfaces can include just dependencies, without injecting
  * CentralSurfaces itself.
  */
-@Module(includes = {StatusBarNotificationPresenterModule.class})
+@Module
 public interface CentralSurfacesDependenciesModule {
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 99d4b2e..27536bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -18,12 +18,14 @@
 
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 
 import dagger.Module;
 
-/** */
-@Module(includes = {StatusBarPhoneModule.class, CentralSurfacesDependenciesModule.class,
+/**  */
+@Module(includes = {CentralSurfacesDependenciesModule.class,
+        StatusBarNotificationPresenterModule.class, StatusBarPhoneModule.class,
         NotificationsModule.class, NotificationRowModule.class})
 public interface CentralSurfacesModule {
 }
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 3f2c818..7c71864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -386,6 +386,7 @@
         }
     }
 
+    @Deprecated("As part of b/301915812")
     private fun scheduleDelayedDozeAmountAnimation() {
         val alreadyRunning = delayedDozeAmountAnimator != null
         logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ae4ba27..29627e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,7 +18,7 @@
 
 import android.os.UserHandle
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.screenshareNotificationHiding
+import com.android.server.notification.Flags.screenshareNotificationHiding
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index cd816ae..954e805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
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 6bba72b..92b0c04 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
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
+import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
@@ -78,14 +79,14 @@
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import javax.inject.Provider;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
+import javax.inject.Provider;
+
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
@@ -94,6 +95,7 @@
         FooterViewModelModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
         NotificationDataLayerModule.class,
+        NotificationDomainLayerModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
         ActivatableNotificationViewModelModule.class,
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 2cac000..b187cf1 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
@@ -17,4 +17,10 @@
 
 import dagger.Module
 
-@Module(includes = []) interface NotificationDataLayerModule
+@Module(
+    includes =
+        [
+            NotificationSettingsRepositoryModule::class,
+        ]
+)
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
new file mode 100644
index 0000000..a7970c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+@Module(includes = [SecureSettingsRepositoryModule::class])
+object NotificationSettingsRepositoryModule {
+    @Provides
+    @SysUISingleton
+    fun provideNotificationSettingsRepository(
+        @Background backgroundScope: CoroutineScope,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        secureSettingsRepository: SecureSettingsRepository,
+    ): NotificationSettingsRepository =
+        NotificationSettingsRepository(
+            backgroundScope,
+            backgroundDispatcher,
+            secureSettingsRepository
+        )
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
similarity index 62%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
index a368de2..5c49b28 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.statusbar.notification.domain
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationSettingsInteractorModule
+import dagger.Module
+
+@Module(includes = [NotificationSettingsInteractorModule::class])
+object NotificationDomainLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
new file mode 100644
index 0000000..0a9e12a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationSettingsInteractorModule {
+    @Provides
+    @SysUISingleton
+    fun provideNotificationSettingsInteractor(repository: NotificationSettingsRepository) =
+        NotificationSettingsInteractor(repository)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3616fd6d..16f18a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -54,7 +54,7 @@
     private static final String TAG = "FooterView";
 
     private FooterViewButton mClearAllButton;
-    private FooterViewButton mManageButton;
+    private FooterViewButton mManageOrHistoryButton;
     private boolean mShowHistory;
     // String cache, for performance reasons.
     // Reading them from a Resources object can be quite slow sometimes.
@@ -68,6 +68,8 @@
 
     private @StringRes int mClearAllButtonTextId;
     private @StringRes int mClearAllButtonDescriptionId;
+    private @StringRes int mManageOrHistoryButtonTextId;
+    private @StringRes int mManageOrHistoryButtonDescriptionId;
     private @StringRes int mMessageStringId;
     private @DrawableRes int mMessageIconId;
 
@@ -155,6 +157,43 @@
         mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
     }
 
+    /** Set the text label for the "Manage"/"History" button. */
+    public void setManageOrHistoryButtonText(@StringRes int textId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+        if (mManageOrHistoryButtonTextId == textId) {
+            return; // nothing changed
+        }
+        mManageOrHistoryButtonTextId = textId;
+        updateManageOrHistoryButtonText();
+    }
+
+    private void updateManageOrHistoryButtonText() {
+        if (mManageOrHistoryButtonTextId == 0) {
+            return; // not initialized yet
+        }
+        mManageOrHistoryButton.setText(getContext().getString(mManageOrHistoryButtonTextId));
+    }
+
+    /** Set the accessibility content description for the "Clear all" button. */
+    public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
+            return; // nothing changed
+        }
+        mManageOrHistoryButtonDescriptionId = contentDescriptionId;
+        updateManageOrHistoryButtonDescription();
+    }
+
+    private void updateManageOrHistoryButtonDescription() {
+        if (mManageOrHistoryButtonDescriptionId == 0) {
+            return; // not initialized yet
+        }
+        mManageOrHistoryButton.setContentDescription(
+                getContext().getString(mManageOrHistoryButtonDescriptionId));
+    }
+
     /** Set the string for a message to be shown instead of the buttons. */
     public void setMessageString(@StringRes int messageId) {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -173,7 +212,6 @@
         mSeenNotifsFooterTextView.setText(messageString);
     }
 
-
     /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
     public void setMessageIcon(@DrawableRes int iconId) {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -203,9 +241,11 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mClearAllButton = (FooterViewButton) findSecondaryView();
-        mManageButton = findViewById(R.id.manage_text);
+        mManageOrHistoryButton = findViewById(R.id.manage_text);
         mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
-        updateResources();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateResources();
+        }
         updateContent();
         updateColors();
     }
@@ -213,11 +253,11 @@
     /** Show a message instead of the footer buttons. */
     public void setFooterLabelVisible(boolean isVisible) {
         if (isVisible) {
-            mManageButton.setVisibility(View.GONE);
+            mManageOrHistoryButton.setVisibility(View.GONE);
             mClearAllButton.setVisibility(View.GONE);
             mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
         } else {
-            mManageButton.setVisibility(View.VISIBLE);
+            mManageOrHistoryButton.setVisibility(View.VISIBLE);
             mClearAllButton.setVisibility(View.VISIBLE);
             mSeenNotifsFooterTextView.setVisibility(View.GONE);
         }
@@ -225,7 +265,7 @@
 
     /** Set onClickListener for the manage/history button. */
     public void setManageButtonClickListener(OnClickListener listener) {
-        mManageButton.setOnClickListener(listener);
+        mManageOrHistoryButton.setOnClickListener(listener);
     }
 
     /** Set onClickListener for the clear all (end) button. */
@@ -252,6 +292,7 @@
 
     /** Show "History" instead of "Manage" on the start button. */
     public void showHistory(boolean showHistory) {
+        FooterViewRefactor.assertInLegacyMode();
         if (mShowHistory == showHistory) {
             return;
         }
@@ -260,17 +301,13 @@
     }
 
     private void updateContent() {
-        if (mShowHistory) {
-            mManageButton.setText(mManageNotificationHistoryText);
-            mManageButton.setContentDescription(mManageNotificationHistoryText);
-        } else {
-            mManageButton.setText(mManageNotificationText);
-            mManageButton.setContentDescription(mManageNotificationText);
-        }
         if (FooterViewRefactor.isEnabled()) {
             updateClearAllButtonText();
             updateClearAllButtonDescription();
 
+            updateManageOrHistoryButtonText();
+            updateManageOrHistoryButtonDescription();
+
             updateMessageString();
             updateMessageIcon();
         } else {
@@ -285,6 +322,14 @@
             // `updateResources`, which will eventually be removed. There are, however, still
             // situations in which we want to update the views even if the resource IDs didn't
             // change, such as configuration changes.
+            if (mShowHistory) {
+                mManageOrHistoryButton.setText(mManageNotificationHistoryText);
+                mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
+            } else {
+                mManageOrHistoryButton.setText(mManageNotificationText);
+                mManageOrHistoryButton.setContentDescription(mManageNotificationText);
+            }
+
             mClearAllButton.setText(R.string.clear_all_notifications_text);
             mClearAllButton.setContentDescription(
                     mContext.getString(R.string.accessibility_clear_all));
@@ -297,6 +342,7 @@
 
     /** Whether the start button shows "History" (true) or "Manage" (false). */
     public boolean isHistoryShown() {
+        FooterViewRefactor.assertInLegacyMode();
         return mShowHistory;
     }
 
@@ -304,7 +350,9 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateColors();
-        updateResources();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateResources();
+        }
         updateContent();
     }
 
@@ -328,23 +376,22 @@
         }
         mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(onSurface);
-        mManageButton.setBackground(manageBg);
-        mManageButton.setTextColor(onSurface);
+        mManageOrHistoryButton.setBackground(manageBg);
+        mManageOrHistoryButton.setTextColor(onSurface);
         mSeenNotifsFooterTextView.setTextColor(onSurface);
         mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
     }
 
     private void updateResources() {
+        FooterViewRefactor.assertInLegacyMode();
         mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
         mManageNotificationHistoryText = getContext()
                 .getString(R.string.manage_notifications_history_text);
-        if (!FooterViewRefactor.isEnabled()) {
-            int unlockIconSize = getResources()
-                    .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
-            mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
-            mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
-            mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
-        }
+        int unlockIconSize = getResources()
+                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+        mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+        mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+        mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index e0eee96..9fb453a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -25,49 +25,136 @@
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /** Binds a [FooterView] to its [view model][FooterViewModel]. */
 object FooterViewBinder {
-    fun bind(
+    fun bindWhileAttached(
         footer: FooterView,
         viewModel: FooterViewModel,
         clearAllNotifications: View.OnClickListener,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener,
     ): DisposableHandle {
+        return footer.repeatWhenAttached {
+            lifecycleScope.launch {
+                bind(
+                    footer,
+                    viewModel,
+                    clearAllNotifications,
+                    launchNotificationSettings,
+                    launchNotificationHistory
+                )
+            }
+        }
+    }
+
+    suspend fun bind(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+        clearAllNotifications: View.OnClickListener,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener
+    ) = coroutineScope {
+        launch {
+            bindClearAllButton(
+                footer,
+                viewModel,
+                clearAllNotifications,
+            )
+        }
+        launch {
+            bindManageOrHistoryButton(
+                footer,
+                viewModel,
+                launchNotificationSettings,
+                launchNotificationHistory
+            )
+        }
+        launch { bindMessage(footer, viewModel) }
+    }
+
+    private suspend fun bindClearAllButton(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+        clearAllNotifications: View.OnClickListener,
+    ) = coroutineScope {
+        footer.setClearAllButtonClickListener(clearAllNotifications)
+
+        launch {
+            viewModel.clearAllButton.labelId.collect { textId ->
+                footer.setClearAllButtonText(textId)
+            }
+        }
+
+        launch {
+            viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+                footer.setClearAllButtonDescription(textId)
+            }
+        }
+
+        launch {
+            viewModel.clearAllButton.isVisible.collect { isVisible ->
+                if (isVisible.isAnimating) {
+                    footer.setClearAllButtonVisible(
+                        isVisible.value,
+                        /* animate = */ true,
+                    ) { _ ->
+                        isVisible.stopAnimating()
+                    }
+                } else {
+                    footer.setClearAllButtonVisible(
+                        isVisible.value,
+                        /* animate = */ false,
+                    )
+                }
+            }
+        }
+    }
+
+    private suspend fun bindManageOrHistoryButton(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener,
+    ) = coroutineScope {
+        launch {
+            viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+                if (shouldLaunchHistory) {
+                    footer.setManageButtonClickListener(launchNotificationHistory)
+                } else {
+                    footer.setManageButtonClickListener(launchNotificationSettings)
+                }
+            }
+        }
+
+        launch {
+            viewModel.manageOrHistoryButton.labelId.collect { textId ->
+                footer.setManageOrHistoryButtonText(textId)
+            }
+        }
+
+        launch {
+            viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+                footer.setManageOrHistoryButtonDescription(textId)
+            }
+        }
+
+        // NOTE: The manage/history button is always visible as long as the footer is visible, no
+        //  need to update the visibility here.
+    }
+
+    private suspend fun bindMessage(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+    ) = coroutineScope {
         // Bind the resource IDs
         footer.setMessageString(viewModel.message.messageId)
         footer.setMessageIcon(viewModel.message.iconId)
-        footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
-        footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
 
-        // Bind the click listeners
-        footer.setClearAllButtonClickListener(clearAllNotifications)
-
-        // Listen for visibility changes when the view is attached.
-        return footer.repeatWhenAttached {
-            lifecycleScope.launch {
-                viewModel.clearAllButton.isVisible.collect { isVisible ->
-                    if (isVisible.isAnimating) {
-                        footer.setClearAllButtonVisible(
-                            isVisible.value,
-                            /* animate = */ true,
-                        ) { _ ->
-                            isVisible.stopAnimating()
-                        }
-                    } else {
-                        footer.setClearAllButtonVisible(
-                            isVisible.value,
-                            /* animate = */ false,
-                        )
-                    }
-                }
-            }
-
-            lifecycleScope.launch {
-                viewModel.message.isVisible.collect { visible ->
-                    footer.setFooterLabelVisible(visible)
-                }
-            }
+        launch {
+            viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 244555a..691dc42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -21,7 +21,7 @@
 import kotlinx.coroutines.flow.Flow
 
 data class FooterButtonViewModel(
-    @StringRes val labelId: Int,
-    @StringRes val accessibilityDescriptionId: Int,
+    @StringRes val labelId: Flow<Int>,
+    @StringRes val accessibilityDescriptionId: Flow<Int>,
     val isVisible: Flow<AnimatedValue<Boolean>>,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e6b0abc..5111c11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,30 +19,36 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
 import com.android.systemui.util.ui.toAnimatedValueFlow
 import dagger.Module
 import dagger.Provides
 import java.util.Optional
 import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for [FooterView]. */
 class FooterViewModel(
     activeNotificationsInteractor: ActiveNotificationsInteractor,
+    notificationSettingsInteractor: NotificationSettingsInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
 ) {
     val clearAllButton: FooterButtonViewModel =
         FooterButtonViewModel(
-            labelId = R.string.clear_all_notifications_text,
-            accessibilityDescriptionId = R.string.accessibility_clear_all,
+            labelId = flowOf(R.string.clear_all_notifications_text),
+            accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
             isVisible =
                 activeNotificationsInteractor.hasClearableNotifications
                     .sample(
@@ -59,6 +65,22 @@
                     .toAnimatedValueFlow(),
         )
 
+    val manageButtonShouldLaunchHistory =
+        notificationSettingsInteractor.isNotificationHistoryEnabled
+
+    private val manageOrHistoryButtonText: Flow<Int> =
+        manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+            if (shouldLaunchHistory) R.string.manage_notifications_history_text
+            else R.string.manage_notifications_text
+        }
+
+    val manageOrHistoryButton: FooterButtonViewModel =
+        FooterButtonViewModel(
+            labelId = manageOrHistoryButtonText,
+            accessibilityDescriptionId = manageOrHistoryButtonText,
+            isVisible = flowOf(AnimatedValue.NotAnimating(true)),
+        )
+
     val message: FooterMessageViewModel =
         FooterMessageViewModel(
             messageId = R.string.unlock_to_see_notif_text,
@@ -73,6 +95,7 @@
     @SysUISingleton
     fun provideOptional(
         activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
+        notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
         seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
         shadeInteractor: Provider<ShadeInteractor>,
     ): Optional<FooterViewModel> {
@@ -80,6 +103,7 @@
             Optional.of(
                 FooterViewModel(
                     activeNotificationsInteractor.get(),
+                    notificationSettingsInteractor.get(),
                     seenNotificationsInteractor.get(),
                     shadeInteractor.get()
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
index 783488af..4e40888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -37,11 +37,12 @@
 ) {
     suspend fun bind(view: NotificationIconContainer) {
         viewModel.icons.bindIcons(
-            view,
-            configuration,
-            systemBarUtilsState,
+            logTag = "shelf",
+            view = view,
+            configuration = configuration,
+            systemBarUtilsState = systemBarUtilsState,
             notifyBindingFailures = { failureTracker.shelfFailures = it },
-            viewStore,
+            viewStore = viewStore,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index f375ebc..de3a626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -72,11 +72,12 @@
             val iconColors: StateFlow<NotificationIconColors> =
                 viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }.stateIn(this)
             viewModel.icons.bindIcons(
-                view,
-                configuration,
-                systemBarUtilsState,
+                logTag = "statusbar",
+                view = view,
+                configuration = configuration,
+                systemBarUtilsState = systemBarUtilsState,
                 notifyBindingFailures = { failureTracker.statusBarFailures = it },
-                viewStore,
+                viewStore = viewStore,
             ) { _, sbiv ->
                 StatusBarIconViewBinder.bindIconColors(
                     sbiv,
@@ -124,11 +125,12 @@
             val tintAlpha = viewModel.tintAlpha.stateIn(this)
             val animsEnabled = viewModel.areIconAnimationsEnabled.stateIn(this)
             viewModel.icons.bindIcons(
-                view,
-                configuration,
-                systemBarUtilsState,
+                logTag = "aod",
+                view = view,
+                configuration = configuration,
+                systemBarUtilsState = systemBarUtilsState,
                 notifyBindingFailures = { failureTracker.aodFailures = it },
-                viewStore,
+                viewStore = viewStore,
             ) { _, sbiv ->
                 coroutineScope {
                     launch { StatusBarIconViewBinder.bindColor(sbiv, color) }
@@ -176,6 +178,7 @@
      * view is to be unbound.
      */
     suspend fun Flow<NotificationIconsViewData>.bindIcons(
+        logTag: String,
         view: NotificationIconContainer,
         configuration: ConfigurationState,
         systemBarUtilsState: SystemBarUtilsState,
@@ -197,7 +200,7 @@
                 }
                 .stateIn(this)
         try {
-            bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
+            bindIcons(logTag, view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
         } finally {
             // Detach everything so that child SBIVs don't hold onto a reference to the container.
             view.detachAllIcons()
@@ -205,6 +208,7 @@
     }
 
     private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+        logTag: String,
         view: NotificationIconContainer,
         layoutParams: StateFlow<FrameLayout.LayoutParams>,
         notifyBindingFailures: (Collection<String>) -> Unit,
@@ -214,7 +218,7 @@
         val failedBindings = mutableSetOf<String>()
         val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
         var prevIcons = NotificationIconsViewData()
-        collectTracingEach("NIC#bindIcons") { iconsData: NotificationIconsViewData ->
+        collectTracingEach({ "NIC($logTag)#bindIcons" }) { iconsData: NotificationIconsViewData ->
             val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
             prevIcons = iconsData
 
@@ -249,7 +253,7 @@
                             if (this !== view) {
                                 Log.wtf(
                                     TAG,
-                                    "StatusBarIconView($notifKey) has an unexpected parent",
+                                    "[$logTag] SBIV($notifKey) has an unexpected parent",
                                 )
                             }
                             // If the container was re-inflated and re-bound, then SBIVs might still
@@ -266,7 +270,9 @@
                                 sbiv,
                                 launch {
                                     launch {
-                                        layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
+                                        layoutParams.collectTracingEach(
+                                            tag = { "[$logTag] SBIV#bindLayoutParams" },
+                                        ) {
                                             if (it != sbiv.layoutParams) {
                                                 sbiv.layoutParams = it
                                             }
@@ -307,7 +313,12 @@
                         val childCount = view.childCount
                         for (i in 0 until childCount) {
                             val actual = view.getChildAt(i)
-                            val expected = expectedChildren[i]
+                            val expected = expectedChildren.getOrNull(i)
+                            if (expected == null) {
+                                Log.wtf(TAG, "[$logTag] Unexpected child $actual")
+                                view.removeView(actual)
+                                continue
+                            }
                             if (actual === expected) {
                                 continue
                             }
@@ -379,3 +390,11 @@
     tag: String,
     crossinline collector: (T) -> Unit,
 ) = collect { traceSection(tag) { collector(it) } }
+
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+    noinline tag: () -> String,
+    crossinline collector: (T) -> Unit,
+) {
+    val lazyTag = lazy(mode = LazyThreadSafetyMode.PUBLICATION, tag)
+    collect { traceSection({ lazyTag.value }) { collector(it) } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
new file mode 100644
index 0000000..c74c396
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.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.interruption
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.util.Log
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+// Class to track avalanche trigger event time.
+@SysUISingleton
+class AvalancheProvider
+@Inject
+constructor(
+        private val broadcastDispatcher: BroadcastDispatcher,
+        private val logger: VisualInterruptionDecisionLogger,
+) {
+    val TAG = "AvalancheProvider"
+    val timeoutMs = 120000
+    var startTime: Long = 0L
+
+    private val avalancheTriggerIntents = mutableSetOf(
+            Intent.ACTION_AIRPLANE_MODE_CHANGED,
+            Intent.ACTION_BOOT_COMPLETED,
+            Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+            Intent.ACTION_USER_SWITCHED
+    )
+
+    private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intent.action in avalancheTriggerIntents) {
+
+                // Ignore when airplane mode turned on
+                if (intent.action == Intent.ACTION_AIRPLANE_MODE_CHANGED
+                        && intent.getBooleanExtra(/* name= */ "state", /* defaultValue */ false)) {
+                    Log.d(TAG, "broadcastReceiver: ignore airplane mode on")
+                    return
+                }
+                Log.d(TAG, "broadcastReceiver received intent.action=" + intent.action)
+                startTime = System.currentTimeMillis()
+            }
+        }
+    }
+
+    fun register() {
+        val intentFilter = IntentFilter()
+        for (intent in avalancheTriggerIntents) {
+            intentFilter.addAction(intent)
+        }
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter)
+    }
+}
\ No newline at end of file
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 8e82442..6836816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
+import android.app.Notification
 import android.app.Notification.BubbleMetadata
+import android.app.Notification.CATEGORY_EVENT
+import android.app.Notification.CATEGORY_REMINDER
 import android.app.Notification.VISIBILITY_PRIVATE
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_HIGH
@@ -224,3 +227,71 @@
     override fun shouldSuppress(entry: NotificationEntry) =
         keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
 }
+
+
+class AvalancheSuppressor(
+    private val avalancheProvider: AvalancheProvider,
+    private val systemClock: SystemClock,
+) : VisualInterruptionFilter(
+        types = setOf(PEEK, PULSE),
+        reason = "avalanche",
+    ) {
+    val TAG = "AvalancheSuppressor"
+
+    override var reason: String = "avalanche"
+        protected set
+
+    enum class State {
+        ALLOW_CONVERSATION_AFTER_AVALANCHE,
+        ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
+        ALLOW_CALLSTYLE,
+        ALLOW_CATEGORY_REMINDER,
+        ALLOW_CATEGORY_EVENT,
+        ALLOW_FSI_WITH_PERMISSION_ON,
+        ALLOW_COLORIZED,
+        SUPPRESS
+    }
+
+    override fun shouldSuppress(entry: NotificationEntry): Boolean {
+        val timeSinceAvalanche = systemClock.currentTimeMillis() - avalancheProvider.startTime
+        val isActive = timeSinceAvalanche < avalancheProvider.timeoutMs
+        val state = calculateState(entry)
+        val suppress = isActive && state == State.SUPPRESS
+        reason = "avalanche suppress=$suppress isActive=$isActive state=$state"
+        return suppress
+    }
+
+    private fun calculateState(entry: NotificationEntry): State  {
+        if (
+            entry.ranking.isConversation &&
+                entry.sbn.notification.`when` > avalancheProvider.startTime
+        ) {
+            return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
+        }
+
+        if (entry.channel?.isImportantConversation == true) {
+            return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME
+        }
+
+        if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) {
+            return State.ALLOW_CALLSTYLE
+        }
+
+        if (entry.sbn.notification.category == CATEGORY_REMINDER) {
+            return State.ALLOW_CATEGORY_REMINDER
+        }
+
+        if (entry.sbn.notification.category == CATEGORY_EVENT) {
+            return State.ALLOW_CATEGORY_EVENT
+        }
+
+        if (entry.sbn.notification.fullScreenIntent != null) {
+            return State.ALLOW_FSI_WITH_PERMISSION_ON
+        }
+
+        if (entry.sbn.notification.isColorized) {
+            return State.ALLOW_COLORIZED
+        }
+        return State.SUPPRESS
+    }
+}
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 6878a1e..dabb18b 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
@@ -33,6 +33,7 @@
 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.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -45,22 +46,25 @@
 class VisualInterruptionDecisionProviderImpl
 @Inject
 constructor(
-    private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
-    private val batteryController: BatteryController,
-    deviceProvisionedController: DeviceProvisionedController,
-    private val eventLog: EventLog,
-    private val globalSettings: GlobalSettings,
-    private val headsUpManager: HeadsUpManager,
-    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
-    keyguardStateController: KeyguardStateController,
-    private val logger: VisualInterruptionDecisionLogger,
-    @Main private val mainHandler: Handler,
-    private val powerManager: PowerManager,
-    private val statusBarStateController: StatusBarStateController,
-    private val systemClock: SystemClock,
-    private val uiEventLogger: UiEventLogger,
-    private val userTracker: UserTracker,
+        private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+        private val batteryController: BatteryController,
+        deviceProvisionedController: DeviceProvisionedController,
+        private val eventLog: EventLog,
+        private val globalSettings: GlobalSettings,
+        private val headsUpManager: HeadsUpManager,
+        private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+        keyguardStateController: KeyguardStateController,
+        private val logger: VisualInterruptionDecisionLogger,
+        @Main private val mainHandler: Handler,
+        private val powerManager: PowerManager,
+        private val statusBarStateController: StatusBarStateController,
+        private val systemClock: SystemClock,
+        private val uiEventLogger: UiEventLogger,
+        private val userTracker: UserTracker,
+        private val avalancheProvider: AvalancheProvider
+
 ) : VisualInterruptionDecisionProvider {
+
     init {
         check(!VisualInterruptionRefactor.isUnexpectedlyInLegacyMode())
     }
@@ -166,6 +170,10 @@
         addFilter(HunJustLaunchedFsiSuppressor())
         addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
 
+        if (NotificationAvalancheSuppression.isEnabled) {
+            addFilter(AvalancheSuppressor(avalancheProvider, systemClock))
+            avalancheProvider.register()
+        }
         started = true
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index 99177c2..195fe78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import android.widget.flags.Flags.notifLinearlayoutOptimized
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
@@ -31,6 +32,7 @@
     precomputedTextViewFactory: PrecomputedTextViewFactory,
     bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
     callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
 ) : NotifRemoteViewsFactoryContainer {
     override val factories: Set<NotifRemoteViewsFactory> = buildSet {
         add(precomputedTextViewFactory)
@@ -40,5 +42,8 @@
         if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
             add(callLayoutSetDataAsyncFactory)
         }
+        if (notifLinearlayoutOptimized()) {
+            add(optimizedLinearLayoutFactory)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationOptimizedLinearLayoutFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationOptimizedLinearLayoutFactory.kt
new file mode 100644
index 0000000..f231fbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationOptimizedLinearLayoutFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.LinearLayout
+import com.android.internal.widget.NotificationOptimizedLinearLayout
+import javax.inject.Inject
+
+class NotificationOptimizedLinearLayoutFactory @Inject constructor() : NotifRemoteViewsFactory {
+    override fun instantiate(
+        row: ExpandableNotificationRow,
+        @NotificationRowContentBinder.InflationFlag layoutType: Int,
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        return when (name) {
+            LinearLayout::class.java.name,
+            LinearLayout::class.java.simpleName -> NotificationOptimizedLinearLayout(context, attrs)
+            else -> null
+        }
+    }
+}
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 b9afb14..0a11eb2 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
@@ -1445,12 +1445,12 @@
         if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
-        final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
         // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
         if (SceneContainerFlag.isEnabled()) {
             // stackY should be driven by scene container, not NSSL
             mAmbientState.setStackY(mTopPadding);
         } else {
+            final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
             mAmbientState.setStackY(stackY);
         }
 
@@ -4698,6 +4698,7 @@
      * this will return false.
      **/
     public boolean isHistoryShown() {
+        FooterViewRefactor.assertInLegacyMode();
         return mFooterView != null && mFooterView.isHistoryShown();
     }
 
@@ -4710,10 +4711,10 @@
         }
         mFooterView = footerView;
         addView(mFooterView, index);
-        if (mManageButtonClickListener != null) {
-            mFooterView.setManageButtonClickListener(mManageButtonClickListener);
-        }
         if (!FooterViewRefactor.isEnabled()) {
+            if (mManageButtonClickListener != null) {
+                mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+            }
             mFooterView.setClearAllButtonClickListener(v -> {
                 if (mFooterClearAllListener != null) {
                     mFooterClearAllListener.onClearAll();
@@ -4794,8 +4795,8 @@
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
-        mFooterView.showHistory(showHistory);
         if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.showHistory(showHistory);
             mFooterView.setClearAllButtonVisible(showDismissView, animate);
             mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
         }
@@ -5490,6 +5491,7 @@
      * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
      */
     public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+        FooterViewRefactor.assertInLegacyMode();
         mManageButtonClickListener = listener;
         if (mFooterView != null) {
             mFooterView.setManageButtonClickListener(mManageButtonClickListener);
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 ed26677..a2ff406 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
@@ -21,8 +21,9 @@
 
 import static com.android.app.animation.Interpolators.STANDARD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Flags.nsslFalsingFix;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -845,11 +846,13 @@
         mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
         mKeyguardBypassController
                 .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
-        mView.setManageButtonClickListener(v -> {
-            if (mNotificationActivityStarter != null) {
-                mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
-            }
-        });
+        if (!FooterViewRefactor.isEnabled()) {
+            mView.setManageButtonClickListener(v -> {
+                if (mNotificationActivityStarter != null) {
+                    mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+                }
+            });
+        }
 
         mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -2052,7 +2055,7 @@
             }
             boolean horizontalSwipeWantsIt = false;
             boolean scrollerWantsIt = false;
-            if (KeyguardShadeMigrationNssl.isEnabled()) {
+            if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled()) {
                 // Reverse the order relative to the else statement. onScrollTouch will reset on an
                 // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
                 if (mLongPressedView == null && !mView.isBeingDragged()
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 a3e0941..b38d619 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
@@ -59,9 +59,6 @@
     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
     public static final int ANIMATION_DURATION_FOLD_TO_AOD =
             AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
-    public static final int ANIMATION_DURATION_PULSE_APPEAR =
-            KeyguardSliceView.DEFAULT_ANIM_DURATION;
-    public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
     public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
     public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
     public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -70,7 +67,6 @@
     private static final int MAX_STAGGER_COUNT = 5;
 
     private final int mGoToFullShadeAppearingTranslation;
-    private final int mPulsingAppearingTranslation;
     @VisibleForTesting
     float mHeadsUpAppearStartAboveScreen;
     private final ExpandableViewState mTmpState = new ExpandableViewState();
@@ -102,9 +98,6 @@
         mGoToFullShadeAppearingTranslation =
                 hostLayout.getContext().getResources().getDimensionPixelSize(
                         R.dimen.go_to_full_shade_appearing_translation);
-        mPulsingAppearingTranslation =
-                hostLayout.getContext().getResources().getDimensionPixelSize(
-                        R.dimen.pulsing_notification_appear_translation);
         mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
                 .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
         mAnimationProperties = new AnimationProperties() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 0197264..311ba83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -28,9 +28,6 @@
     /** The bounds of the notification stack in the current scene. */
     val stackBounds = MutableStateFlow(NotificationContainerBounds())
 
-    /** The corner radius of the notification stack, in dp. */
-    val cornerRadiusDp = MutableStateFlow(32f)
-
     /**
      * The height in px of the contents of notification stack. Depending on the number of
      * notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 8307397..9984ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,9 +34,6 @@
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
 
-    /** The corner radius of the notification stack, in dp. */
-    val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
-
     /**
      * The height in px of the contents of notification stack. Depending on the number of
      * notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 44a7e7e..4d65b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -45,6 +46,7 @@
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -58,9 +60,11 @@
     private val configuration: ConfigurationState,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
+    private val loggerOptional: Optional<NotificationStatsLogger>,
     private val metricsLogger: MetricsLogger,
     private val nicBinder: NotificationIconContainerShelfViewBinder,
-    private val loggerOptional: Optional<NotificationStatsLogger>,
+    // Using a provider to avoid a circular dependency.
+    private val notificationActivityStarter: Provider<NotificationActivityStarter>,
     private val viewModel: NotificationListViewModel,
 ) {
 
@@ -115,7 +119,7 @@
             ) { footerView: FooterView ->
                 traceSection("bind FooterView") {
                     val disposableHandle =
-                        FooterViewBinder.bind(
+                        FooterViewBinder.bindWhileAttached(
                             footerView,
                             footerViewModel,
                             clearAllNotifications = {
@@ -124,6 +128,16 @@
                                 )
                                 parentView.clearAllNotifications()
                             },
+                            launchNotificationSettings = { view ->
+                                notificationActivityStarter
+                                    .get()
+                                    .startHistoryIntent(view, /* showHistory = */ false)
+                            },
+                            launchNotificationHistory = { view ->
+                                notificationActivityStarter
+                                    .get()
+                                    .startHistoryIntent(view, /* showHistory = */ true)
+                            },
                         )
                     parentView.setFooterView(footerView)
                     return@reinflateAndBindLatest disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 6c2cbbe..814146c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -26,10 +26,12 @@
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
 import kotlin.math.roundToInt
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
 object NotificationStackAppearanceViewBinder {
+    const val SCRIM_CORNER_RADIUS = 32f
 
     @JvmStatic
     fun bind(
@@ -38,8 +40,8 @@
         viewModel: NotificationStackAppearanceViewModel,
         ambientState: AmbientState,
         controller: NotificationStackScrollLayoutController,
-    ) {
-        view.repeatWhenAttached {
+    ): DisposableHandle {
+        return view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
                     viewModel.stackBounds.collect { bounds ->
@@ -48,8 +50,8 @@
                             bounds.top.roundToInt(),
                             bounds.right.roundToInt(),
                             bounds.bottom.roundToInt(),
-                            viewModel.cornerRadiusDp.value.dpToPx(context),
-                            viewModel.cornerRadiusDp.value.dpToPx(context),
+                            SCRIM_CORNER_RADIUS.dpToPx(context),
+                            0,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index fe5bdd4..f3d0d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -136,8 +136,12 @@
                             .collect { y -> controller.setTranslationY(y) }
                     }
 
-                    launch {
-                        viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
+                    if (!sceneContainerFlags.isEnabled()) {
+                        launch {
+                            viewModel.expansionAlpha.collect {
+                                controller.setMaxAlphaForExpansion(it)
+                            }
+                        }
                     }
                     launch {
                         viewModel.glanceableHubAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 56ff7f9..bdf1a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -74,9 +74,6 @@
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
 
-    /** The corner radius of the notification stack, in dp. */
-    val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
-
     /** The y-coordinate in px of top of the contents of the notification stack. */
     val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index a436f17..65d9c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -20,13 +20,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 
 /**
  * ViewModel used by the Notification placeholders inside the scene container to update the
@@ -40,9 +41,11 @@
     shadeInteractor: ShadeInteractor,
     flags: SceneContainerFlags,
     featureFlags: FeatureFlagsClassic,
+    private val keyguardInteractor: KeyguardInteractor,
 ) {
     /** DEBUG: whether the placeholder "Notifications" text should be shown. */
-    val isPlaceholderTextVisible: Boolean = !flags.flexiNotifsEnabled()
+    val isPlaceholderTextVisible: Boolean =
+        !flags.flexiNotifsEnabled() && SceneContainerFlag.isEnabled
 
     /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
     val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
@@ -64,12 +67,12 @@
         right: Float,
         bottom: Float,
     ) {
-        interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom))
+        val notificationContainerBounds =
+            NotificationContainerBounds(top = top, bottom = bottom, left = left, right = right)
+        keyguardInteractor.setNotificationContainerBounds(notificationContainerBounds)
+        interactor.setStackBounds(notificationContainerBounds)
     }
 
-    /** The corner radius of the placeholder, in dp. */
-    val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
-
     /**
      * The height in px of the contents of notification stack. Depending on the number of
      * notifications, this can exceed the space available on screen to show notifications, at which
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 3915c376..811da51 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
@@ -21,6 +21,7 @@
 
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -31,20 +32,24 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
@@ -70,6 +75,7 @@
 import kotlinx.coroutines.isActive
 
 /** View-model for the shared notification container, used by both the shade and keyguard spaces */
+@SysUISingleton
 class SharedNotificationContainerViewModel
 @Inject
 constructor(
@@ -80,6 +86,9 @@
     private val shadeInteractor: ShadeInteractor,
     communalInteractor: CommunalInteractor,
     private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+    alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+    primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
     dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -94,6 +103,12 @@
         mapOf<Edge?, Flow<Float>>(
             Edge(from = LOCKSCREEN, to = DREAMING) to
                 lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+            Edge(from = LOCKSCREEN, to = GONE) to
+                lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+            Edge(from = ALTERNATE_BOUNCER, to = GONE) to
+                alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+            Edge(from = PRIMARY_BOUNCER, to = GONE) to
+                primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
             Edge(from = DREAMING, to = LOCKSCREEN) to
                 dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
             Edge(from = LOCKSCREEN, to = OCCLUDED) to
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 39ca7b2..3669ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -352,7 +352,7 @@
         }
 
         if (!mKeyguardStateController.isShowing()) {
-            final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+            final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext, mUserTracker.getUserId());
             cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
             mActivityStarter.startActivityDismissingKeyguard(cameraIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
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 64fcef5..3e7089c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,6 +28,7 @@
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.Flags.lightRevealMigration;
+import static com.android.systemui.Flags.newAodTransition;
 import static com.android.systemui.Flags.predictiveBackSysui;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -1803,10 +1804,10 @@
         }
 
         pw.println("Camera gesture intents:");
-        pw.println("   Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext));
-        pw.println("   Secure camera: " + CameraIntents.getSecureCameraIntent(mContext));
+        pw.println("   Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext, mUserTracker.getUserId()));
+        pw.println("   Secure camera: " + CameraIntents.getSecureCameraIntent(mContext, mUserTracker.getUserId()));
         pw.println("   Override package: "
-                + CameraIntents.getOverrideCameraPackage(mContext));
+                + CameraIntents.getOverrideCameraPackage(mContext, mUserTracker.getUserId()));
     }
 
     private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
@@ -2497,7 +2498,8 @@
             mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
                 mDeviceInteractive = true;
 
-                if (shouldAnimateDozeWakeup()) {
+                boolean isFlaggedOff = newAodTransition() && KeyguardShadeMigrationNssl.isEnabled();
+                if (!isFlaggedOff && shouldAnimateDozeWakeup()) {
                     // If this is false, the power button must be physically pressed in order to
                     // trigger fingerprint authentication.
                     final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 877bd7c..e84b7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -44,6 +44,8 @@
 import com.android.app.tracing.traceSection
 import com.android.systemui.BottomMarginCommand
 import com.android.systemui.StatusBarInsetsCommand
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import java.io.PrintWriter
 import java.lang.Math.max
@@ -69,6 +71,7 @@
     val configurationController: ConfigurationController,
     val dumpManager: DumpManager,
     val commandRegistry: CommandRegistry,
+    val sysUICutoutProvider: SysUICutoutProvider,
 ) : CallbackController<StatusBarContentInsetsChangedListener>,
         ConfigurationController.ConfigurationListener,
         Dumpable {
@@ -176,7 +179,8 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val displayCutout = checkNotNull(context.display).cutout
+            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+            val displayCutout = sysUICutout?.cutout
             val key = getCacheKey(rotation, displayCutout)
 
             val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -187,7 +191,7 @@
             val width = point.logicalWidth(rotation)
 
             val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
-                rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+                rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
 
             Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
         }
@@ -212,10 +216,11 @@
     fun getStatusBarContentAreaForRotation(
         @Rotation rotation: Int
     ): Rect {
-        val displayCutout = checkNotNull(context.display).cutout
+        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+        val displayCutout = sysUICutout?.cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
-                rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+                rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
     }
 
     /**
@@ -228,18 +233,18 @@
 
     private fun getAndSetCalculatedAreaForRotation(
         @Rotation targetRotation: Int,
-        displayCutout: DisplayCutout?,
+        sysUICutout: SysUICutoutInformation?,
         rotatedResources: Resources,
         key: CacheKey
     ): Rect {
-        return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources)
+        return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources)
                 .also {
                     insetsCache.put(key, it)
                 }
     }
 
     private fun getCalculatedAreaForRotation(
-        displayCutout: DisplayCutout?,
+        sysUICutout: SysUICutoutInformation?,
         @Rotation targetRotation: Int,
         rotatedResources: Resources
     ): Rect {
@@ -271,7 +276,7 @@
         return calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
-                displayCutout,
+                sysUICutout,
                 context.resources.configuration.windowConfiguration.maxBounds,
                 SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
                 minLeft,
@@ -415,7 +420,7 @@
 fun calculateInsetsForRotationWithRotatedResources(
     @Rotation currentRotation: Int,
     @Rotation targetRotation: Int,
-    displayCutout: DisplayCutout?,
+    sysUICutout: SysUICutoutInformation?,
     maxBounds: Rect,
     statusBarHeight: Int,
     minLeft: Int,
@@ -434,7 +439,7 @@
     val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
 
     return getStatusBarContentBounds(
-            displayCutout,
+            sysUICutout,
             statusBarHeight,
             rotZeroBounds.right,
             rotZeroBounds.bottom,
@@ -470,7 +475,7 @@
  * rotation
  */
 private fun getStatusBarContentBounds(
-        displayCutout: DisplayCutout?,
+        sysUICutout: SysUICutoutInformation?,
         sbHeight: Int,
         width: Int,
         height: Int,
@@ -489,19 +494,17 @@
 
     val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
 
-    val cutoutRects = displayCutout?.boundingRects
-    if (cutoutRects == null || cutoutRects.isEmpty()) {
-        return Rect(minLeft,
-                insetTop,
-                logicalDisplayWidth - minRight,
-                sbHeight)
+    val cutoutRects = sysUICutout?.cutout?.boundingRects
+    if (cutoutRects.isNullOrEmpty()) {
+        return Rect(minLeft, insetTop, logicalDisplayWidth - minRight, sbHeight)
     }
 
-    val relativeRotation = if (currentRotation - targetRotation < 0) {
-        currentRotation - targetRotation + 4
-    } else {
-        currentRotation - targetRotation
-    }
+    val relativeRotation =
+        if (currentRotation - targetRotation < 0) {
+            currentRotation - targetRotation + 4
+        } else {
+            currentRotation - targetRotation
+        }
 
     // Size of the status bar window for the given rotation relative to our exact rotation
     val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
@@ -509,19 +512,26 @@
     var leftMargin = minLeft
     var rightMargin = minRight
     for (cutoutRect in cutoutRects) {
+        val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+        val actualCutoutRect =
+            if (protectionRect?.intersects(cutoutRect) == true) {
+                rectUnion(cutoutRect, protectionRect)
+            } else {
+                cutoutRect
+            }
         // There is at most one non-functional area per short edge of the device. So if the status
         // bar doesn't share a short edge with the cutout, we can ignore its insets because there
         // will be no letter-boxing to worry about
-        if (!shareShortEdge(sbRect, cutoutRect, cWidth, cHeight)) {
+        if (!shareShortEdge(sbRect, actualCutoutRect, cWidth, cHeight)) {
             continue
         }
 
-        if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
-            var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+        if (actualCutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
+            var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
             if (isRtl) logicalWidth += dotWidth
             leftMargin = max(logicalWidth, leftMargin)
-        } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
-            var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+        } else if (actualCutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
+            var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
             if (!isRtl) logicalWidth += dotWidth
             rightMargin = max(rightMargin, logicalWidth)
         }
@@ -532,6 +542,11 @@
     return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
 }
 
+private fun rectUnion(first: Rect, second: Rect) = Rect(first).apply { union(second) }
+
+private fun Rect.intersects(other: Rect): Boolean =
+    intersects(other.left, other.top, other.right, other.bottom)
+
 /*
  * Returns the inset top of the status bar.
  *
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 4c83ca2..3ad60d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,6 +24,7 @@
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -71,6 +72,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.keyguard.shared.model.DismissAction;
@@ -345,6 +347,7 @@
         }
     };
     private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
+    private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
     private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
 
     @Inject
@@ -377,7 +380,8 @@
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
             Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
-            SelectedUserInteractor selectedUserInteractor
+            SelectedUserInteractor selectedUserInteractor,
+            Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -410,6 +414,7 @@
         mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
         mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
         mSelectedUserInteractor = selectedUserInteractor;
+        mSurfaceBehindInteractor = surfaceBehindInteractor;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -480,7 +485,16 @@
             mShadeViewController.postToView(() ->
                     collectFlow(
                         getViewRootImpl().getView(),
-                        mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
+                        combineFlows(
+                                mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
+                                mSurfaceBehindInteractor.get().isAnimatingSurface(),
+                                (lockscreenVis, animatingSurface) ->
+                                        // TODO(b/322546110): Waiting until we're not animating the
+                                        // surface is a workaround to avoid jank. We should actually
+                                        // fix the source of the jank, and then hide the keyguard
+                                        // view without waiting for the animation to end.
+                                        lockscreenVis || animatingSurface
+                        ),
                         this::consumeShowStatusBarKeyguardView));
         }
     }
@@ -1428,10 +1442,6 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
-
-        if (KeyguardWmStateRefactor.isEnabled()) {
-            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
-        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 1a17e7c..665a571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.app.tracing.TraceUtils
+import com.android.app.tracing.namedRunnable
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 
@@ -125,7 +125,7 @@
     }
 
     // FrameCallback used to delay starting the light reveal animation until the next frame
-    private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") {
+    private val startLightRevealCallback = namedRunnable("startLightReveal") {
         lightRevealAnimationPlaying = true
         lightRevealAnimator.start()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 3741f14..c0e36b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -91,7 +91,9 @@
     @StatusBarFragmentScope
     @Named(OPERATOR_NAME_VIEW)
     static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
-        return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+        View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+        operatorName.setVisibility(View.GONE);
+        return operatorName;
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 3c4ca44..d4c180d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,26 +16,38 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
 
+import android.annotation.MainThread;
+import android.app.IActivityManager;
+import android.content.Context;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.Trace;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.Assert;
 import com.android.systemui.util.ListenerSet;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 /** Implementation of SensitiveNotificationProtectionController. **/
 @SysUISingleton
 public class SensitiveNotificationProtectionControllerImpl
         implements SensitiveNotificationProtectionController {
-    private final MediaProjectionManager mMediaProjectionManager;
+    private static final String LOG_TAG = "SNPC";
+    private final ArraySet<String> mExemptPackages = new ArraySet<>();
     private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
     private volatile MediaProjectionInfo mProjection;
 
@@ -44,31 +56,100 @@
             new MediaProjectionManager.Callback() {
                 @Override
                 public void onStart(MediaProjectionInfo info) {
-                    Trace.beginSection(
-                            "SNPC.onProjectionStart");
-                    mProjection = info;
-                    mListeners.forEach(Runnable::run);
-                    Trace.endSection();
+                    Trace.beginSection("SNPC.onProjectionStart");
+                    try {
+                        // Only enable sensitive content protection if sharing full screen
+                        // Launch cookie only set (non-null) if sharing single app/task
+                        updateProjectionStateAndNotifyListeners(
+                                (info.getLaunchCookie() == null) ? info : null);
+                    } finally {
+                        Trace.endSection();
+                    }
                 }
 
                 @Override
                 public void onStop(MediaProjectionInfo info) {
-                    Trace.beginSection(
-                            "SNPC.onProjectionStop");
-                    mProjection = null;
-                    mListeners.forEach(Runnable::run);
-                    Trace.endSection();
+                    Trace.beginSection("SNPC.onProjectionStop");
+                    try {
+                        updateProjectionStateAndNotifyListeners(null);
+                    } finally {
+                        Trace.endSection();
+                    }
                 }
             };
 
     @Inject
     public SensitiveNotificationProtectionControllerImpl(
+            Context context,
             MediaProjectionManager mediaProjectionManager,
-            @Main Handler mainHandler) {
-        mMediaProjectionManager = mediaProjectionManager;
+            IActivityManager activityManager,
+            @Main Handler mainHandler,
+            @Background Executor bgExecutor) {
+        if (!screenshareNotificationHiding()) {
+            return;
+        }
 
-        if (screenshareNotificationHiding()) {
-            mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+        bgExecutor.execute(() -> {
+            ArraySet<String> exemptPackages = new ArraySet<>();
+            // Exempt SystemUI
+            exemptPackages.add(context.getPackageName());
+
+            // Exempt approved bug report handlers
+            try {
+                exemptPackages.addAll(activityManager.getBugreportWhitelistedPackages());
+            } catch (RemoteException e) {
+                Log.e(
+                        LOG_TAG,
+                        "Error adding bug report handlers to exemption, continuing without",
+                        e);
+                // silent failure, skip adding packages to exemption
+            }
+
+            // if currently projecting, notify listeners of exemption changes
+            mainHandler.post(() -> {
+                Trace.beginSection("SNPC.exemptPackagesUpdated");
+                try {
+                    updateExemptPackagesAndNotifyListeners(exemptPackages);
+                } finally {
+                    Trace.endSection();
+                }
+            });
+        });
+
+        mediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+    }
+
+    /**
+     * Notify listeners of possible ProjectionState change regardless of current
+     * isSensitiveStateActive value. Method used to ensure updates occur after mExemptPackages gets
+     * updated, which directly changes the outcome of isSensitiveStateActive
+     */
+    @MainThread
+    private void updateExemptPackagesAndNotifyListeners(ArraySet<String> exemptPackages) {
+        Assert.isMainThread();
+        mExemptPackages.addAll(exemptPackages);
+
+        if (mProjection != null) {
+            mListeners.forEach(Runnable::run);
+        }
+    }
+
+    /**
+     * Update ProjectionState respecting current isSensitiveStateActive value. Only notifies
+     * listeners
+     */
+    @MainThread
+    private void updateProjectionStateAndNotifyListeners(MediaProjectionInfo info) {
+        Assert.isMainThread();
+        // capture previous state
+        boolean wasSensitive = isSensitiveStateActive();
+
+        // update internal state
+        mProjection = info;
+
+        // if either previous or new state is sensitive, notify listeners.
+        if (wasSensitive || isSensitiveStateActive()) {
+            mListeners.forEach(Runnable::run);
         }
     }
 
@@ -82,12 +163,17 @@
         mListeners.remove(onSensitiveStateChanged);
     }
 
+    // TODO(b/323396693): opportunity for optimization
     @Override
     public boolean isSensitiveStateActive() {
+        MediaProjectionInfo projection = mProjection;
+        if (projection == null) {
+            return false;
+        }
+
         // TODO(b/316955558): Add disabled by developer option
-        // TODO(b/316955306): Add feature exemption for sysui and bug handlers
-        // TODO(b/316955346): Add feature exemption for single app screen sharing
-        return mProjection != null;
+
+        return !mExemptPackages.contains(projection.getPackageName());
     }
 
     @Override
@@ -96,9 +182,18 @@
             return false;
         }
 
+        MediaProjectionInfo projection = mProjection;
+        if (projection == null) {
+            return false;
+        }
+
         // Exempt foreground service notifications from protection in effort to keep screen share
         // stop actions easily accessible
-        // TODO(b/316955208): Exempt FGS notifications only for app that started projection
-        return !entry.getSbn().getNotification().isFgsOrUij();
+        StatusBarNotification sbn = entry.getSbn();
+        if (sbn.getNotification().isFgsOrUij()) {
+            return !sbn.getPackageName().equals(projection.getPackageName());
+        }
+
+        return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
new file mode 100644
index 0000000..32cf86d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.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.statusbar.policy.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates device-provisioning related business logic. */
+@SysUISingleton
+class DeviceProvisioningInteractor
+@Inject
+constructor(
+    repository: DeviceProvisioningRepository,
+) {
+    /**
+     * Whether this device has been provisioned.
+     *
+     * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+     */
+    val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+    /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
+    val isFactoryResetProtectionActive: Flow<Boolean> = repository.isFactoryResetProtectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index 92a64a6..4dfd5a1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import android.util.Log
-import com.android.app.tracing.TraceUtils.instantForTrack
 import com.android.app.tracing.TraceUtils.traceAsync
+import com.android.app.tracing.instantForTrack
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -81,8 +81,8 @@
                 .pairwise()
                 .filter {
                     // Start tracking only when the foldable device is
-                    //folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
-                    //unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
+                    // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
+                    // unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
                     foldableDeviceState ->
                     foldableDeviceState.previousValue == DeviceState.FOLDED ||
                         foldableDeviceState.newValue == DeviceState.FOLDED
@@ -172,7 +172,7 @@
         fromFoldableDeviceState: Int
     ): DisplaySwitchLatencyEvent {
         log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
-        instantForTrack(TAG, "fromFoldableDeviceState=$fromFoldableDeviceState")
+        instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" }
 
         return copy(fromFoldableDeviceState = fromFoldableDeviceState)
     }
@@ -187,7 +187,7 @@
                 "toState=$toState, " +
                 "latencyMs=$displaySwitchTimeMs"
         }
-        instantForTrack(TAG, "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState")
+        instantForTrack(TAG) { "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState" }
 
         return copy(
             toFoldableDeviceState = toFoldableDeviceState,
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
index ceebcb7..e5179dd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
@@ -37,19 +37,13 @@
      */
     fun registerTask(name: String): Runnable {
         pendingTasksCount.incrementAndGet()
-
-        if (ENABLE_TRACE) {
-            Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
-        }
+        Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
 
         return Runnable {
+            Trace.endAsyncSection("PendingTasksContainer#$name", 0)
             if (pendingTasksCount.decrementAndGet() == 0) {
                 val onComplete = completionCallback.getAndSet(null)
                 onComplete?.run()
-
-                if (ENABLE_TRACE) {
-                    Trace.endAsyncSection("PendingTasksContainer#$name", 0)
-                }
             }
         }
     }
@@ -82,4 +76,3 @@
     fun getPendingCount(): Int = pendingTasksCount.get()
 }
 
-private const val ENABLE_TRACE = false
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
new file mode 100644
index 0000000..ab6a37b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.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.util.kotlin
+
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+
+object SharedPreferencesExt {
+    /**
+     * Returns a flow of [Unit] that is invoked each time shared preference is updated.
+     *
+     * @param key Optional key to limit updates to a particular key.
+     */
+    fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
+        conflatedCallbackFlow {
+                val listener =
+                    SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
+                registerOnSharedPreferenceChangeListener(listener)
+                awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+            }
+            .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
index 6993c96..fa0d030 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
+import android.content.Context
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 
@@ -110,3 +111,11 @@
     val fifth: E,
     val sixth: F,
 )
+
+fun Int.toPx(context: Context): Int {
+    return (this * context.resources.displayMetrics.density).toInt()
+}
+
+fun Int.toDp(context: Context): Int {
+    return (this / context.resources.displayMetrics.density).toInt()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index f36c335e..d509b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.util.settings;
 
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
+
 import dagger.Binds;
 import dagger.Module;
 
@@ -36,4 +39,9 @@
     /** Bind GlobalSettingsImpl to GlobalSettings. */
     @Binds
     GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
+
+    /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
+    @Binds
+    UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
+            UserAwareSecureSettingsRepositoryImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
new file mode 100644
index 0000000..d3e5080
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxy
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+interface UserAwareSecureSettingsRepository {
+
+    /**
+     * Emits boolean value of the setting for active user. Also emits starting value when
+     * subscribed.
+     * See: [SettingsProxy.getBool].
+     */
+    fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
+}
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
+    private val secureSettings: SecureSettings,
+    private val userRepository: UserRepository,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : UserAwareSecureSettingsRepository {
+
+    override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
+        return secureSettings
+            .observerFlow(userId, name)
+            .onStart { emit(Unit) }
+            .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8d5e55a..ff1daea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.dagger
 
+import android.content.Context
 import android.media.AudioManager
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -35,10 +36,12 @@
 
         @Provides
         fun provideAudioRepository(
+            @Application context: Context,
             audioManager: AudioManager,
             @Background coroutineContext: CoroutineContext,
             @Application coroutineScope: CoroutineScope,
-        ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+        ): AudioRepository =
+            AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
 
         @Provides
         fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
new file mode 100644
index 0000000..ea67eea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.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.volume.dagger
+
+import android.view.accessibility.CaptioningManager
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepositoryImpl
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface CaptioningModule {
+
+    companion object {
+
+        @Provides
+        fun provideCaptioningRepository(
+            captioningManager: CaptioningManager,
+            @Background coroutineContext: CoroutineContext,
+            @Application coroutineScope: CoroutineScope,
+        ): CaptioningRepository =
+            CaptioningRepositoryImpl(captioningManager, coroutineContext, coroutineScope)
+
+        @Provides
+        fun provideCaptioningInteractor(repository: CaptioningRepository): CaptioningInteractor =
+            CaptioningInteractor(repository)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
new file mode 100644
index 0000000..2ff9af9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import android.content.Context
+import android.media.session.MediaSessionManager
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface MediaDevicesModule {
+
+    companion object {
+
+        @Provides
+        @SysUISingleton
+        fun provideMediaDeviceSessionRepository(
+            @Application context: Context,
+            mediaSessionManager: MediaSessionManager,
+            localBluetoothManager: LocalBluetoothManager?,
+            @Application coroutineScope: CoroutineScope,
+            @Background backgroundContext: CoroutineContext,
+        ): MediaControllerRepository =
+            MediaControllerRepositoryImpl(
+                context,
+                mediaSessionManager,
+                localBluetoothManager,
+                coroutineScope,
+                backgroundContext,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c842e5f..2718839 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -62,6 +62,8 @@
 @Module(
         includes = {
                 AudioModule.class,
+                CaptioningModule.class,
+                MediaDevicesModule.class
         },
         subcomponents = {
                 VolumePanelComponent.class
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
new file mode 100644
index 0000000..57ac435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.volume.panel.component.mediaoutput.data.repository
+
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+class LocalMediaRepositoryFactory
+@Inject
+constructor(
+    private val localMediaManagerFactory: LocalMediaManagerFactory,
+    @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
+) {
+
+    fun create(packageName: String?): LocalMediaRepository =
+        LocalMediaRepositoryImpl(
+            localMediaManagerFactory.create(packageName),
+            coroutineScope,
+            backgroundCoroutineContext,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
new file mode 100644
index 0000000..6c456f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import android.content.pm.PackageManager
+import android.util.Log
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class MediaOutputInteractor
+@Inject
+constructor(
+    private val localMediaRepositoryFactory: LocalMediaRepositoryFactory,
+    private val packageManager: PackageManager,
+    @VolumePanelScope private val coroutineScope: CoroutineScope,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
+    mediaControllerRepository: MediaControllerRepository
+) {
+
+    val mediaDeviceSession: Flow<MediaDeviceSession> =
+        mediaControllerRepository.activeMediaController.mapNotNull { mediaController ->
+            if (mediaController == null) {
+                MediaDeviceSession.Inactive
+            } else {
+                MediaDeviceSession.Active(
+                    appLabel = getApplicationLabel(mediaController.packageName)
+                            ?: return@mapNotNull null,
+                    packageName = mediaController.packageName,
+                    sessionToken = mediaController.sessionToken,
+                )
+            }
+        }
+    private val localMediaRepository: Flow<LocalMediaRepository> =
+        mediaDeviceSession
+            .map { (it as? MediaDeviceSession.Active)?.packageName }
+            .distinctUntilChanged()
+            .map { localMediaRepositoryFactory.create(it) }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 1)
+
+    val currentConnectedDevice: Flow<MediaDevice?> =
+        localMediaRepository.flatMapLatest { it.currentConnectedDevice }
+
+    val mediaDevices: Flow<Collection<MediaDevice>> =
+        localMediaRepository.flatMapLatest { it.mediaDevices }
+
+    private suspend fun getApplicationLabel(packageName: String): CharSequence? {
+        return try {
+            withContext(backgroundCoroutineContext) {
+                val appInfo =
+                    packageManager.getApplicationInfo(
+                        packageName,
+                        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+                    )
+                appInfo.loadLabel(packageManager)
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(TAG, "Unable to find info for package: $packageName")
+            null
+        }
+    }
+
+    private companion object {
+        const val TAG = "MediaOutputInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
new file mode 100644
index 0000000..f250308
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.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.volume.panel.component.mediaoutput.domain.model
+
+import android.media.session.MediaSession
+
+/** Represents media playing on the connected device. */
+sealed interface MediaDeviceSession {
+
+    /** Media is playing. */
+    data class Active(
+        val appLabel: CharSequence,
+        val packageName: String,
+        val sessionToken: MediaSession.Token,
+    ) : MediaDeviceSession
+
+    /** Media is not playing. */
+    data object Inactive : MediaDeviceSession
+
+    /** Current media state is unknown yet. */
+    data object Unknown : MediaDeviceSession
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 2acd4b9..139d190 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -111,6 +111,7 @@
     // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
     private final List<NotifCallback> mCallbacks = new ArrayList<>();
     private final StatusBarWindowCallback mStatusBarWindowCallback;
+    private boolean mPanelExpanded;
 
     /**
      * Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present
@@ -242,8 +243,12 @@
         // Store callback in a field so it won't get GC'd
         mStatusBarWindowCallback =
                 (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing,
-                        panelExpanded, isDreaming) ->
+                        panelExpanded, isDreaming) -> {
+                    if (panelExpanded != mPanelExpanded) {
+                        mPanelExpanded = panelExpanded;
                         mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
+                    }
+                };
         notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
 
         mSysuiProxy = new Bubbles.SysuiProxy() {
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
index ec0414e..88939a2 100644
--- a/packages/SystemUI/tests/Android.bp
+++ b/packages/SystemUI/tests/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index da6bfe8..d742da7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -47,7 +47,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -167,9 +166,7 @@
                 mVibrator,
                 mAuthRippleController,
                 mResources,
-                KeyguardTransitionInteractorFactory.create(
-                        TestScopeProvider.getTestScope().getBackgroundScope())
-                                .getKeyguardTransitionInteractor(),
+                mKosmos.getKeyguardTransitionInteractor(),
                 KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
                 mFeatureFlags,
                 mPrimaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index e921a59..f776a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -344,10 +344,15 @@
     }
 
     private fun createAndStartSut(): CameraAvailabilityListener {
-        return CameraAvailabilityListener.build(context, context.mainExecutor).also {
-            it.addTransitionCallback(cameraTransitionCallback)
-            it.startListening()
-        }
+        return CameraAvailabilityListener.build(
+                context,
+                context.mainExecutor,
+                CameraProtectionLoaderImpl((context))
+        )
+            .also {
+                it.addTransitionCallback(cameraTransitionCallback)
+                it.startListening()
+            }
     }
 
     private class TestCameraTransitionCallback :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
new file mode 100644
index 0000000..a19a0c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CameraProtectionLoaderImplTest : SysuiTestCase() {
+
+    private val loader = CameraProtectionLoaderImpl(context)
+
+    @Before
+    fun setUp() {
+        overrideResource(R.string.config_protectedCameraId, OUTER_CAMERA_LOGICAL_ID)
+        overrideResource(R.string.config_protectedPhysicalCameraId, OUTER_CAMERA_PHYSICAL_ID)
+        overrideResource(
+            R.string.config_frontBuiltInDisplayCutoutProtection,
+            OUTER_CAMERA_PROTECTION_PATH
+        )
+        overrideResource(R.string.config_protectedScreenUniqueId, OUTER_SCREEN_UNIQUE_ID)
+        overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
+        overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
+        overrideResource(
+            R.string.config_innerBuiltInDisplayCutoutProtection,
+            INNER_CAMERA_PROTECTION_PATH
+        )
+        overrideResource(R.string.config_protectedInnerScreenUniqueId, INNER_SCREEN_UNIQUE_ID)
+    }
+
+    @Test
+    fun loadCameraProtectionInfoList() {
+        val protectionList = loadProtectionList()
+
+        assertThat(protectionList)
+            .containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
+    }
+
+    @Test
+    fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
+        overrideResource(R.string.config_protectedCameraId, "")
+
+        val protectionList = loadProtectionList()
+
+        assertThat(protectionList).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+    }
+
+    @Test
+    fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
+        overrideResource(R.string.config_protectedInnerCameraId, "")
+
+        val protectionList = loadProtectionList()
+
+        assertThat(protectionList).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+    }
+
+    @Test
+    fun loadCameraProtectionInfoList_innerAndOuterCameraIdsEmpty_returnsEmpty() {
+        overrideResource(R.string.config_protectedCameraId, "")
+        overrideResource(R.string.config_protectedInnerCameraId, "")
+
+        val protectionList = loadProtectionList()
+
+        assertThat(protectionList).isEmpty()
+    }
+
+    private fun loadProtectionList() =
+        loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+    private fun CameraProtectionInfo.toTestableVersion() =
+        TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
+
+    /**
+     * "Testable" version, because the original version contains a Path property, which doesn't
+     * implement equals.
+     */
+    private data class TestableProtectionInfo(
+        val logicalCameraId: String,
+        val physicalCameraId: String?,
+        val cutoutBounds: Rect,
+        val displayUniqueId: String?,
+    )
+
+    companion object {
+        private const val OUTER_CAMERA_LOGICAL_ID = "1"
+        private const val OUTER_CAMERA_PHYSICAL_ID = "11"
+        private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+        private val OUTER_CAMERA_PROTECTION_BOUNDS =
+            Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+        private const val OUTER_SCREEN_UNIQUE_ID = "111"
+        private val OUTER_CAMERA_PROTECTION_INFO =
+            TestableProtectionInfo(
+                OUTER_CAMERA_LOGICAL_ID,
+                OUTER_CAMERA_PHYSICAL_ID,
+                OUTER_CAMERA_PROTECTION_BOUNDS,
+                OUTER_SCREEN_UNIQUE_ID,
+            )
+
+        private const val INNER_CAMERA_LOGICAL_ID = "2"
+        private const val INNER_CAMERA_PHYSICAL_ID = "22"
+        private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+        private val INNER_CAMERA_PROTECTION_BOUNDS =
+            Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+        private const val INNER_SCREEN_UNIQUE_ID = "222"
+        private val INNER_CAMERA_PROTECTION_INFO =
+            TestableProtectionInfo(
+                INNER_CAMERA_LOGICAL_ID,
+                INNER_CAMERA_PHYSICAL_ID,
+                INNER_CAMERA_PROTECTION_BOUNDS,
+                INNER_SCREEN_UNIQUE_ID,
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
new file mode 100644
index 0000000..f769b4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import com.android.systemui.res.R
+
+class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
+    CameraProtectionLoader {
+
+    private val realLoader = CameraProtectionLoaderImpl(context)
+
+    override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> =
+        realLoader.loadCameraProtectionInfoList()
+
+    fun clearProtectionInfoList() {
+        context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "")
+        context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "")
+    }
+
+    fun addAllProtections() {
+        addOuterCameraProtection()
+        addInnerCameraProtection()
+    }
+
+    fun addOuterCameraProtection(displayUniqueId: String = "111") {
+        context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
+        context.orCreateTestableResources.addOverride(
+            R.string.config_protectedPhysicalCameraId,
+            "11"
+        )
+        context.orCreateTestableResources.addOverride(
+            R.string.config_frontBuiltInDisplayCutoutProtection,
+            "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+        )
+        context.orCreateTestableResources.addOverride(
+            R.string.config_protectedScreenUniqueId,
+            displayUniqueId
+        )
+    }
+
+    fun addInnerCameraProtection(displayUniqueId: String = "222") {
+        context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
+        context.orCreateTestableResources.addOverride(
+            R.string.config_protectedInnerPhysicalCameraId,
+            "22"
+        )
+        context.orCreateTestableResources.addOverride(
+            R.string.config_innerBuiltInDisplayCutoutProtection,
+            "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+        )
+        context.orCreateTestableResources.addOverride(
+            R.string.config_protectedInnerScreenUniqueId,
+            displayUniqueId
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c07148b..c20367e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -176,6 +176,8 @@
     private FakeFacePropertyRepository mFakeFacePropertyRepository =
             new FakeFacePropertyRepository();
     private List<DecorProvider> mMockCutoutList;
+    private final CameraProtectionLoader mCameraProtectionLoader =
+            new CameraProtectionLoaderImpl(mContext);
 
     @Before
     public void setup() {
@@ -247,7 +249,7 @@
                 mThreadFactory,
                 mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
-                mFakeFacePropertyRepository, mJavaAdapter) {
+                mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader) {
             @Override
             public void start() {
                 super.start();
@@ -1243,7 +1245,7 @@
                 mDotViewController,
                 mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
-                mFakeFacePropertyRepository, mJavaAdapter);
+                mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader);
         screenDecorations.start();
         when(mContext.getDisplay()).thenReturn(mDisplay);
         when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
new file mode 100644
index 0000000..f37c4ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUICutoutProviderTest : SysuiTestCase() {
+
+    private val fakeProtectionLoader = FakeCameraProtectionLoader(context)
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() {
+        val noCutoutDisplay = createDisplay(cutout = null)
+        val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
+        val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+
+        assertThat(sysUICutout).isNull()
+    }
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_returnsCutout() {
+        val cutoutDisplay = createDisplay()
+        val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+        val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+        assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
+    }
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() {
+        val cutoutDisplay = createDisplay()
+        val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+        val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+        assertThat(sysUICutout.cameraProtection).isNull()
+    }
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() {
+        fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID)
+        val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+        val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+        assertThat(sysUICutout.cameraProtection).isNotNull()
+    }
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() {
+        fakeProtectionLoader.clearProtectionInfoList()
+        val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+        val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+        assertThat(sysUICutout.cameraProtection).isNull()
+    }
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() {
+        fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+        val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
+        val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+        assertThat(sysUICutout.cameraProtection).isNull()
+    }
+
+    @Test
+    fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() {
+        fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+        val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
+        val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+        assertThat(sysUICutout.cameraProtection).isNull()
+    }
+
+    companion object {
+        private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
+        private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
+
+        private fun createDisplay(
+            uniqueId: String? = "uniqueId",
+            cutout: DisplayCutout? = mock<DisplayCutout>()
+        ) =
+            mock<Display> {
+                whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
+                whenever(this.cutout).thenReturn(cutout)
+                whenever(this.uniqueId).thenReturn(uniqueId)
+            }
+    }
+}
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 8299acb..375ebe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,6 +51,7 @@
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.animation.AccelerateInterpolator;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -79,6 +80,7 @@
 
 @LargeTest
 @RunWith(AndroidTestingRunner.class)
+@FlakyTest(bugId = 308501761)
 public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
 
     @Rule
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 ca3eb3e..4a1bdbc 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
@@ -69,7 +69,6 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
@@ -238,7 +237,7 @@
         final List<String> stubShortcutTargets = new ArrayList<>();
         stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
         when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
-                ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
+                AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets);
 
         mMenuViewLayer.mDismissMenuAction.run();
         final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 0ba9abe..f490f3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -45,7 +45,7 @@
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -89,7 +89,7 @@
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var qsController: QuickSettingsController
-    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var windowRootView: WindowRootView
     @Mock private lateinit var viewRootImpl: ViewRootImpl
@@ -123,7 +123,7 @@
                 notificationShadeWindowController,
                 windowRootViewVisibilityInteractor
             )
-            .apply { this.setup(qsController, shadeViewController) }
+            .apply { this.setup(qsController, shadeBackActionInteractor) }
     }
 
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -165,19 +165,19 @@
         val result = backActionInteractor.onBackRequested()
 
         assertTrue(result)
-        verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+        verify(shadeBackActionInteractor, atLeastOnce()).animateCollapseQs(anyBoolean())
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
     }
 
     @Test
     fun testOnBackRequested_closeUserSwitcherIfOpen() {
-        whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+        whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
 
         val result = backActionInteractor.onBackRequested()
 
         assertTrue(result)
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
-        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+        verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
     }
 
     @Test
@@ -189,7 +189,7 @@
 
         assertFalse(result)
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
-        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+        verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
     }
 
     @Test
@@ -290,11 +290,11 @@
         powerInteractor.setAwakeForTest()
         val callback = getBackInvokedCallback() as OnBackAnimationCallback
 
-        whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+        whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(false)
 
         callback.onBackProgressed(createBackEvent(0.3f))
 
-        verify(shadeViewController, never()).onBackProgressed(0.3f)
+        verify(shadeBackActionInteractor, never()).onBackProgressed(0.3f)
     }
 
     @Test
@@ -305,11 +305,11 @@
         powerInteractor.setAwakeForTest()
         val callback = getBackInvokedCallback() as OnBackAnimationCallback
 
-        whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+        whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(true)
 
         callback.onBackProgressed(createBackEvent(0.4f))
 
-        verify(shadeViewController).onBackProgressed(0.4f)
+        verify(shadeBackActionInteractor).onBackProgressed(0.4f)
     }
 
     private fun getBackInvokedCallback(): OnBackInvokedCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 8f0e910..8fbeb6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.AuthenticationStateListener
+import android.hardware.biometrics.BiometricFingerprintConstants
 import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
@@ -24,11 +25,13 @@
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
@@ -167,6 +170,28 @@
             listener.onAuthenticationStopped()
             assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
         }
+
+    @Test
+    fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+        testScope.runTest {
+            val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+            listener.onAuthenticationAcquired(
+                BiometricSourceType.FINGERPRINT,
+                REASON_AUTH_BP,
+                BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+            )
+
+            assertThat(fingerprintAcquiredStatus)
+                .isEqualTo(
+                    AcquiredFingerprintAuthenticationStatus(
+                        AuthenticationReason.BiometricPromptAuthentication,
+                        BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                    )
+                )
+        }
 }
 
 private fun BiometricManager.captureListener() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index a84778a..eae953e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -16,11 +16,9 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.os.Handler
 import android.util.Size
 import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.DisplayInfo
 import android.view.Surface
 import androidx.test.filters.SmallTest
@@ -29,61 +27,37 @@
 import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.FakeDeviceStateRepository
+import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.same
-import org.mockito.Captor
-import org.mockito.Mock
 import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
-private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class DisplayStateRepositoryTest : SysuiTestCase() {
-    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
-    @Mock private lateinit var deviceStateManager: DeviceStateManager
-    @Mock private lateinit var displayManager: DisplayManager
-    @Mock private lateinit var handler: Handler
-    @Mock private lateinit var display: Display
-    private lateinit var underTest: DisplayStateRepository
-
+    private val display = mock<Display>()
     private val testScope = TestScope(StandardTestDispatcher())
-    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val fakeDeviceStateRepository = FakeDeviceStateRepository()
+    private val fakeDisplayRepository = FakeDisplayRepository()
 
-    @Captor
-    private lateinit var displayListenerCaptor: ArgumentCaptor<DisplayManager.DisplayListener>
+    private lateinit var underTest: DisplayStateRepository
 
     @Before
     fun setUp() {
-        val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.array.config_rearDisplayDeviceStates,
-            rearDisplayDeviceStates
-        )
-
         mContext.orCreateTestableResources.addOverride(
             com.android.internal.R.bool.config_reverseDefaultRotation,
             false
@@ -96,11 +70,8 @@
             DisplayStateRepositoryImpl(
                 testScope.backgroundScope,
                 mContext,
-                deviceStateManager,
-                displayManager,
-                handler,
-                fakeExecutor,
-                UnconfinedTestDispatcher(),
+                fakeDeviceStateRepository,
+                fakeDisplayRepository,
             )
     }
 
@@ -110,12 +81,10 @@
             val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode)
             runCurrent()
 
-            val callback = deviceStateManager.captureCallback()
-
-            callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+            fakeDeviceStateRepository.emit(DeviceState.FOLDED)
             assertThat(isInRearDisplayMode).isFalse()
 
-            callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+            fakeDeviceStateRepository.emit(DeviceState.REAR_DISPLAY)
             assertThat(isInRearDisplayMode).isTrue()
         }
 
@@ -125,19 +94,13 @@
             val currentRotation by collectLastValue(underTest.currentRotation)
             runCurrent()
 
-            verify(displayManager)
-                .registerDisplayListener(
-                    displayListenerCaptor.capture(),
-                    same(handler),
-                    eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
-                )
-
             whenever(display.getDisplayInfo(any())).then {
                 val info = it.getArgument<DisplayInfo>(0)
                 info.rotation = Surface.ROTATION_90
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_90)
+
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)
 
             whenever(display.getDisplayInfo(any())).then {
@@ -145,7 +108,8 @@
                 info.rotation = Surface.ROTATION_180
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
         }
 
@@ -155,13 +119,6 @@
             val currentSize by collectLastValue(underTest.currentDisplaySize)
             runCurrent()
 
-            verify(displayManager)
-                .registerDisplayListener(
-                    displayListenerCaptor.capture(),
-                    same(handler),
-                    eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
-                )
-
             whenever(display.getDisplayInfo(any())).then {
                 val info = it.getArgument<DisplayInfo>(0)
                 info.rotation = Surface.ROTATION_0
@@ -169,7 +126,7 @@
                 info.logicalHeight = 200
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentSize).isEqualTo(Size(100, 200))
 
             whenever(display.getDisplayInfo(any())).then {
@@ -179,12 +136,7 @@
                 info.logicalHeight = 200
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentSize).isEqualTo(Size(200, 100))
         }
 }
-
-private fun DeviceStateManager.captureCallback() =
-    withArgCaptor<DeviceStateManager.DeviceStateCallback> {
-        verify(this@captureCallback).registerCallback(any(), capture())
-    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index d7b7d79..5c34fd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,12 +19,14 @@
 import android.app.ActivityManager
 import android.app.ActivityTaskManager
 import android.content.ComponentName
+import android.hardware.biometrics.BiometricFingerprintConstants
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -162,6 +164,27 @@
             )
             assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
         }
+
+    @Test
+    fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+        testScope.runTest {
+            val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+            runCurrent()
+
+            biometricStatusRepository.setFingerprintAcquiredStatus(
+                AcquiredFingerprintAuthenticationStatus(
+                    AuthenticationReason.BiometricPromptAuthentication,
+                    BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                )
+            )
+            assertThat(fingerprintAcquiredStatus)
+                .isEqualTo(
+                    AcquiredFingerprintAuthenticationStatus(
+                        AuthenticationReason.BiometricPromptAuthentication,
+                        BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                    )
+                )
+        }
 }
 
 private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index fc34255..3f83ce3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.display.data.repository.fakeDeviceStateRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -47,7 +47,6 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class LogContextInteractorImplTest : SysuiTestCase() {
-
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
     private val kosmos = testKosmos()
@@ -64,11 +63,7 @@
             LogContextInteractorImpl(
                 testScope.backgroundScope,
                 deviceStateRepository,
-                KeyguardTransitionInteractorFactory.create(
-                        repository = keyguardTransitionRepository,
-                        scope = testScope.backgroundScope,
-                    )
-                    .keyguardTransitionInteractor,
+                kosmos.keyguardTransitionInteractor,
                 udfpsOverlayInteractor,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 3603c3c..5509c04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -100,6 +101,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
@@ -253,7 +255,8 @@
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
-                mock(),
+                biometricStatusInteractor,
+                kosmos.deviceEntryFingerprintAuthInteractor,
                 sfpsSensorInteractor,
                 kosmos.dozeServiceHost,
                 kosmos.keyguardInteractor,
@@ -426,6 +429,54 @@
         }
     }
 
+    // On progress bar shown - hide indicator
+    // On progress bar hidden - show indicator
+    @Test
+    fun verifyIndicatorProgressBarInteraction() {
+        testScope.runTest {
+            // Pre-auth conditions
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            // Show primary bouncer
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            val inOrder = inOrder(windowManager)
+
+            // Verify indicator shown
+            inOrder.verify(windowManager).addView(any(), any())
+
+            // Set progress bar visible
+            sideFpsProgressBarViewModel.setVisible(true)
+
+            runCurrent()
+
+            // Verify indicator hidden
+            inOrder.verify(windowManager).removeView(any())
+
+            // Set progress bar invisible
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            runCurrent()
+
+            // Verify indicator shown
+            inOrder.verify(windowManager).addView(any(), any())
+        }
+    }
+
     private fun updatePrimaryBouncer(
         isShowing: Boolean,
         isAnimatingAway: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6a9c881..2e94d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -81,6 +81,7 @@
 private const val CHALLENGE = 2L
 private const val DELAY = 1000L
 private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -1246,6 +1247,14 @@
         }
 
     @Test
+    fun logoIsNullIfPackageNameNotFound() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val logo by collectLastValue(viewModel.logo)
+            assertThat(logo).isNull()
+        }
+
+    @Test
     fun defaultLogoIfNoLogoSet() = runGenericTest {
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val logo by collectLastValue(viewModel.logo)
@@ -1291,7 +1300,8 @@
         contentView: PromptContentView? = null,
         logoRes: Int = -1,
         logoBitmap: Bitmap? = null,
-        block: suspend TestScope.() -> Unit
+        packageName: String = OP_PACKAGE_NAME,
+        block: suspend TestScope.() -> Unit,
     ) {
         selector.initializePrompt(
             requireConfirmation = testCase.confirmationRequested,
@@ -1302,6 +1312,7 @@
             contentViewFromApp = contentView,
             logoResFromApp = logoRes,
             logoBitmapFromApp = logoBitmap,
+            packageName = packageName,
         )
 
         // put the view model in the initial authenticating state, unless explicitly skipped
@@ -1481,6 +1492,7 @@
     contentViewFromApp: PromptContentView? = null,
     logoResFromApp: Int = -1,
     logoBitmapFromApp: Bitmap? = null,
+    packageName: String = OP_PACKAGE_NAME,
 ) {
     val info =
         PromptInfo().apply {
@@ -1500,7 +1512,7 @@
         USER_ID,
         CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
-        OP_PACKAGE_NAME,
+        packageName,
     )
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 3c43031..2014755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
-import android.app.ActivityTaskManager
 import android.content.res.Configuration.UI_MODE_NIGHT_NO
 import android.content.res.Configuration.UI_MODE_NIGHT_YES
 import android.graphics.Color
@@ -39,10 +38,10 @@
 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.domain.interactor.BiometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -80,7 +79,6 @@
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -109,7 +107,6 @@
     private val kosmos = testKosmos()
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
 
-    @Mock private lateinit var activityTaskManager: ActivityTaskManager
     @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     @Mock
     private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
@@ -147,7 +144,6 @@
         context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400)
 
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
     private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor
     private lateinit var displayStateInteractor: DisplayStateInteractorImpl
     private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@@ -184,6 +180,7 @@
             .thenReturn(
                 Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
             )
+        kosmos.biometricStatusRepository = biometricStatusRepository
 
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -197,9 +194,6 @@
                 testScope.backgroundScope,
             )
 
-        biometricStatusInteractor =
-            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
-
         displayStateInteractor =
             DisplayStateInteractorImpl(
                 testScope.backgroundScope,
@@ -256,6 +250,7 @@
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
+                kosmos.biometricStatusInteractor,
                 kosmos.deviceEntryFingerprintAuthInteractor,
                 sfpsSensorInteractor,
                 kosmos.dozeServiceHost,
@@ -263,13 +258,13 @@
                 displayStateInteractor,
                 kosmos.testDispatcher,
                 testScope.backgroundScope,
-                kosmos.powerInteractor,
+                kosmos.powerInteractor
             )
 
         underTest =
             SideFpsOverlayViewModel(
                 mContext,
-                biometricStatusInteractor,
+                kosmos.biometricStatusInteractor,
                 deviceEntrySideFpsOverlayInteractor,
                 displayStateInteractor,
                 sfpsSensorInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 6d3cc4c..669795b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -81,10 +81,10 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(cameraIntents.getSecureCameraIntent()).thenReturn(
+        whenever(cameraIntents.getSecureCameraIntent(anyInt())).thenReturn(
             Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
         )
-        whenever(cameraIntents.getInsecureCameraIntent()).thenReturn(
+        whenever(cameraIntents.getInsecureCameraIntent(anyInt())).thenReturn(
             Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
         )
 
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 9c5cd71..20dd913 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
@@ -77,6 +77,18 @@
         }
 
     @Test
+    fun deleteWidget_notInDb_returnsFalse() =
+        testScope.runTest {
+            val (widgetId, provider, priority) = widgetInfo1
+            communalWidgetDao.addWidget(
+                widgetId = widgetId,
+                provider = provider,
+                priority = priority,
+            )
+            assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
+        }
+
+    @Test
     fun addWidget_emitsActiveWidgetsInDb(): Unit =
         testScope.runTest {
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c98d537..de455f63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,11 +34,12 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -69,12 +70,13 @@
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import java.io.File
-import java.util.*
+import java.util.Optional
 import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ControlsControllerImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     @Mock
     private lateinit var uiController: ControlsUiController
@@ -109,8 +111,6 @@
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
 
-    private val preferredPanelRepository = FakeSelectedComponentRepository()
-
     private lateinit var delayableExecutor: FakeExecutor
     private lateinit var controller: ControlsControllerImpl
     private lateinit var canceller: DidRunRunnable
@@ -171,7 +171,7 @@
                 wrapper,
                 delayableExecutor,
                 uiController,
-                preferredPanelRepository,
+                kosmos.selectedComponentRepository,
                 bindingController,
                 listingController,
                 userFileManager,
@@ -225,7 +225,7 @@
                 mContext,
                 delayableExecutor,
                 uiController,
-                preferredPanelRepository,
+                kosmos.selectedComponentRepository,
                 bindingController,
                 listingController,
                 userFileManager,
@@ -245,7 +245,7 @@
                 mContext,
                 delayableExecutor,
                 uiController,
-                preferredPanelRepository,
+                kosmos.selectedComponentRepository,
                 bindingController,
                 listingController,
                 userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 4828ba3..18ce4a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -18,36 +18,40 @@
 package com.android.systemui.controls.panels
 
 import android.content.SharedPreferences
+import android.content.pm.UserInfo
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.io.File
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
 
-    @Mock private lateinit var userTracker: UserTracker
+    private lateinit var userTracker: FakeUserTracker
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         mContext.orCreateTestableResources.addOverride(
             R.array.config_controlsPreferredPackages,
             arrayOf<String>()
         )
-        whenever(userTracker.userId).thenReturn(0)
+        userTracker = kosmos.fakeUserTracker.apply { set(listOf(PRIMARY_USER, SECONDARY_USER), 0) }
     }
 
     @Test
@@ -91,7 +95,7 @@
         val repository = createRepository(fileManager)
 
         assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
-        whenever(userTracker.userId).thenReturn(1)
+        userTracker.set(listOf(SECONDARY_USER), 0)
         assertThat(repository.getAuthorizedPanels()).isEmpty()
     }
 
@@ -127,6 +131,51 @@
         assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
     }
 
+    @Test
+    fun observeAuthorizedPanels() =
+        testScope.runTest {
+            val sharedPrefs = FakeSharedPreferences()
+            val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+            val repository = createRepository(fileManager)
+
+            val authorizedPanels by
+                collectLastValue(repository.observeAuthorizedPanels(PRIMARY_USER.userHandle))
+            assertThat(authorizedPanels).isEmpty()
+
+            repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+
+            repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).isEmpty()
+        }
+
+    @Test
+    fun observeAuthorizedPanelsForAnotherUser() =
+        testScope.runTest {
+            val fileManager =
+                FakeUserFileManager(
+                    mapOf(
+                        0 to FakeSharedPreferences(),
+                        1 to FakeSharedPreferences(),
+                    )
+                )
+            val repository = createRepository(fileManager)
+
+            val authorizedPanels by
+                collectLastValue(repository.observeAuthorizedPanels(SECONDARY_USER.userHandle))
+            assertThat(authorizedPanels).isEmpty()
+
+            // Primary user is active, add authorized panels.
+            repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).isEmpty()
+
+            // Make secondary user active and add authorized panels again.
+            userTracker.set(listOf(PRIMARY_USER, SECONDARY_USER), 1)
+            assertThat(authorizedPanels).isEmpty()
+            repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+        }
+
     private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
         return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
     }
@@ -153,5 +202,9 @@
         private const val FILE_NAME = "controls_prefs"
         private const val KEY = "authorized_panels"
         private const val TEST_PACKAGE = "package"
+        private val PRIMARY_USER =
+            UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+        private val SECONDARY_USER =
+            UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index b463adf..a7e7ba9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -23,8 +23,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.UserFileManager
@@ -74,7 +72,6 @@
     @Mock private lateinit var userTracker: UserTracker
     private lateinit var userFileManager: UserFileManager
 
-    private val featureFlags = FakeFeatureFlags()
     // under test
     private lateinit var repository: SelectedComponentRepository
 
@@ -95,11 +92,9 @@
                 )
             repository =
                 SelectedComponentRepositoryImpl(
-                    userFileManager,
-                    userTracker,
-                    featureFlags,
+                    userFileManager = userFileManager,
+                    userTracker = userTracker,
                     bgDispatcher = testDispatcher,
-                    applicationScope = applicationCoroutineScope
                 )
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index bcef67e..c44429b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -31,15 +31,15 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.data.repository.fakePackageChangeRepository
-import com.android.systemui.common.data.repository.packageChangeRepository
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.domain.interactor.packageChangeInteractor
+import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
@@ -87,7 +87,7 @@
     @Mock private lateinit var userManager: UserManager
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
 
-    private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
+    private lateinit var preferredPanelsRepository: SelectedComponentRepository
 
     private lateinit var fakeExecutor: FakeExecutor
 
@@ -99,7 +99,7 @@
         whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
 
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        preferredPanelsRepository = FakeSelectedComponentRepository()
+        preferredPanelsRepository = kosmos.selectedComponentRepository
     }
 
     @Test
@@ -444,7 +444,7 @@
             userTracker,
             authorizedPanelsRepository,
             preferredPanelsRepository,
-            kosmos.packageChangeRepository,
+            kosmos.packageChangeInteractor,
             userManager,
             broadcastDispatcher,
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 36ae0c7..8f3813d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -43,8 +43,8 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
@@ -53,6 +53,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSystemUIDialogController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -85,6 +86,8 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class ControlsUiControllerImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     @Mock lateinit var controlsController: ControlsController
     @Mock lateinit var controlsListingController: ControlsListingController
     @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@@ -100,7 +103,7 @@
     @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
 
-    private val preferredPanelRepository = FakeSelectedComponentRepository()
+    private val preferredPanelRepository = kosmos.selectedComponentRepository
     private lateinit var fakeDialogController: FakeSystemUIDialogController
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val bgExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 368d1d9..b28d0c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -16,71 +16,56 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
-import android.app.trust.TrustManager
 import android.content.pm.UserInfo
 import android.hardware.biometrics.BiometricFaceConstants
 import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.keyguard.trustManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FaceSensorInfo
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.facePropertyRepository
 import com.android.systemui.biometrics.shared.model.LockoutMode
 import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -89,84 +74,39 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
+    val kosmos =
+        testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() }
 
     private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
-    private lateinit var testScope: TestScope
-    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
-    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
-    private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
-    private lateinit var fakeUserRepository: FakeUserRepository
-    private lateinit var facePropertyRepository: FakeFacePropertyRepository
-    private lateinit var fakeDeviceEntryFingerprintAuthRepository:
-        FakeDeviceEntryFingerprintAuthRepository
-    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
-    private lateinit var powerInteractor: PowerInteractor
-    private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
+    private val testScope = kosmos.testScope
+    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+    private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+    private val fakeUserRepository = kosmos.fakeUserRepository
+    private val facePropertyRepository = kosmos.facePropertyRepository
+    private val fakeDeviceEntryFingerprintAuthRepository =
+        kosmos.fakeDeviceEntryFingerprintAuthRepository
+    private val powerInteractor = kosmos.powerInteractor
+    private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
 
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
-    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var trustManager: TrustManager
+    private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+    private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig
+    private val trustManager = kosmos.trustManager
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        val scheduler = TestCoroutineScheduler()
-        val dispatcher = StandardTestDispatcher(scheduler)
-        testScope = TestScope(dispatcher)
-        bouncerRepository = FakeKeyguardBouncerRepository()
-        faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-        keyguardTransitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = TestScope().backgroundScope,
-                    repository = keyguardTransitionRepository,
-                )
-                .keyguardTransitionInteractor
-
-        fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
-        fakeUserRepository = FakeUserRepository()
         fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
-        facePropertyRepository = FakeFacePropertyRepository()
-        fakeKeyguardRepository = FakeKeyguardRepository()
-        powerInteractor = PowerInteractorFactory.create().powerInteractor
-        fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
 
         underTest =
             SystemUIDeviceEntryFaceAuthInteractor(
                 mContext,
                 testScope.backgroundScope,
-                dispatcher,
+                kosmos.testDispatcher,
                 faceAuthRepository,
-                {
-                    PrimaryBouncerInteractor(
-                        bouncerRepository,
-                        mock(BouncerView::class.java),
-                        mock(Handler::class.java),
-                        mock(KeyguardStateController::class.java),
-                        mock(KeyguardSecurityModel::class.java),
-                        mock(PrimaryBouncerCallbackInteractor::class.java),
-                        mock(FalsingCollector::class.java),
-                        mock(DismissCallbackRegistry::class.java),
-                        context,
-                        keyguardUpdateMonitor,
-                        FakeTrustRepository(),
-                        testScope.backgroundScope,
-                        selectedUserInteractor,
-                        underTest,
-                    )
-                },
-                AlternateBouncerInteractor(
-                    mock(StatusBarStateController::class.java),
-                    mock(KeyguardStateController::class.java),
-                    bouncerRepository,
-                    FakeFingerprintPropertyRepository(),
-                    fakeBiometricSettingsRepository,
-                    FakeSystemClock(),
-                    keyguardUpdateMonitor,
-                    testScope.backgroundScope,
-                ),
+                { kosmos.primaryBouncerInteractor },
+                kosmos.alternateBouncerInteractor,
                 keyguardTransitionInteractor,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index af027e8..6d2df19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -63,6 +63,7 @@
         mDockHandler = new DozeDockHandler(mConfig, mDockManagerFake, mUserTracker);
         mDockHandler.setDozeMachine(mMachine);
 
+        when(mMachine.isExecutingTransition()).thenReturn(false);
         when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
         when(mMachine.getState()).thenReturn(State.DOZE_AOD);
         doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
@@ -148,4 +149,13 @@
 
         verify(mMachine, never()).requestState(any(State.class));
     }
+
+    @Test
+    public void onEvent_dockedWhileTransitioning_wontRequestStateChange() {
+        when(mMachine.isExecutingTransition()).thenReturn(true);
+
+        mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+
+        verify(mMachine, never()).requestState(any(State.class));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
deleted file mode 100644
index ed80a86..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.android.systemui.keyboard.stickykeys.data.repository
-
-import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
-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.stickykeys.StickyKeysLogger
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class StickyKeysRepositoryImplTest : SysuiTestCase() {
-
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-    private val secureSettings = FakeSettings()
-    private val userRepository = Kosmos().fakeUserRepository
-    private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
-
-    @Before
-    fun setup() {
-        stickyKeysRepository = StickyKeysRepositoryImpl(
-            mock<InputManager>(),
-            dispatcher,
-            secureSettings,
-            userRepository,
-            mock<StickyKeysLogger>()
-        )
-        userRepository.setUserInfos(USER_INFOS)
-        setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
-        setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
-    }
-
-    @Test
-    fun settingEnabledEmitsValueForCurrentUser() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-
-            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
-
-            assertThat(enabled).isTrue()
-        }
-    }
-
-    @Test
-    fun settingEnabledEmitsNewValueWhenSettingChanges() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectValues(stickyKeysRepository.settingEnabled)
-            runCurrent()
-
-            setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
-
-            assertThat(enabled).containsExactly(true, false).inOrder()
-        }
-    }
-
-    @Test
-    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
-            runCurrent()
-
-            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
-
-            assertThat(enabled).isFalse()
-        }
-    }
-
-    private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
-        val newValue = if (enabled) "1" else "0"
-        secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
-    }
-
-    private companion object {
-        val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
-        val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
-        val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
index df73cc8..a992956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyboard.stickykeys.ui
 
+import android.app.Dialog
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.compose.ComposeFacade
@@ -26,8 +27,6 @@
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -40,8 +39,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
 
@@ -53,15 +50,13 @@
     private lateinit var coordinator: StickyKeysIndicatorCoordinator
     private val testScope = TestScope(StandardTestDispatcher())
     private val stickyKeysRepository = FakeStickyKeysRepository()
-    private val dialog = mock<ComponentSystemUIDialog>()
+    private val dialog = mock<Dialog>()
 
     @Before
     fun setup() {
         Assume.assumeTrue(ComposeFacade.isComposeAvailable())
-        val dialogFactory = mock<SystemUIDialogFactory> {
-            whenever(applicationContext).thenReturn(context)
-            whenever(create(any(), anyInt(), anyBoolean())).thenReturn(dialog)
-        }
+        val dialogFactory = mock<StickyKeyDialogFactory>()
+        whenever(dialogFactory.create(any())).thenReturn(dialog)
         val keyboardRepository = Kosmos().keyboardRepository
         val viewModel = StickyKeysIndicatorViewModel(
                 stickyKeysRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 6eebb6d..d14d72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -68,11 +69,15 @@
 
     @Before
     fun setup() {
+        val settingsRepository = UserAwareSecureSettingsRepositoryImpl(
+            secureSettings,
+            userRepository,
+            dispatcher
+        )
         val stickyKeysRepository = StickyKeysRepositoryImpl(
             inputManager,
             dispatcher,
-            secureSettings,
-            userRepository,
+            settingsRepository,
             mock<StickyKeysLogger>()
         )
         setStickyKeySetting(enabled = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 852f9a5..cf8fe79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -5,16 +5,16 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.utils.GlobalWindowManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,13 +36,14 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ResourceTrimmerTest : SysuiTestCase() {
+    val kosmos = testKosmos()
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
-    private val keyguardRepository = FakeKeyguardRepository()
-    private val featureFlags = FakeFeatureFlags()
-    private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-    private lateinit var powerInteractor: PowerInteractor
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val featureFlags = kosmos.fakeFeatureFlagsClassic
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val powerInteractor = kosmos.powerInteractor
 
     @Mock private lateinit var globalWindowManager: GlobalWindowManager
     private lateinit var resourceTrimmer: ResourceTrimmer
@@ -52,7 +53,6 @@
         MockitoAnnotations.initMocks(this)
         featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
         featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
-        powerInteractor = PowerInteractorFactory.create().powerInteractor
         keyguardRepository.setDozeAmount(0f)
         keyguardRepository.setKeyguardGoingAway(false)
 
@@ -66,11 +66,7 @@
             ResourceTrimmer(
                 keyguardInteractor,
                 powerInteractor,
-                KeyguardTransitionInteractorFactory.create(
-                        scope = TestScope().backgroundScope,
-                        repository = keyguardTransitionRepository,
-                    )
-                    .keyguardTransitionInteractor,
+                kosmos.keyguardTransitionInteractor,
                 globalWindowManager,
                 testScope.backgroundScope,
                 testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
new file mode 100644
index 0000000..c174cb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromGoneTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromGoneTransitionInteractor
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        underTest.start()
+    }
+
+    @Test
+    fun testDoesNotTransitionToLockscreen_ifStartedButNotFinishedInGone() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GONE,
+                        transitionState = TransitionState.STARTED,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GONE,
+                        transitionState = TransitionState.RUNNING,
+                    ),
+                ),
+                testScope,
+            )
+            reset(keyguardTransitionRepository)
+            kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // We're in the middle of a LOCKSCREEN -> GONE transition.
+            assertThat(keyguardTransitionRepository).noTransitionsStarted()
+        }
+
+    @Test
+    fun testTransitionsToLockscreen_ifFinishedInGone() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            reset(keyguardTransitionRepository)
+            kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // We're in the middle of a LOCKSCREEN -> GONE transition.
+            assertThat(keyguardTransitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 722c11d..6d8e7aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -16,89 +16,58 @@
 
 package com.android.systemui.keyguard.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.SysUITestModule
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.util.mockTopActivityClassName
-import com.android.systemui.shared.system.ActivityManagerWrapper
-import dagger.BindsInstance
-import dagger.Component
-import dagger.Lazy
-import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import junit.framework.Assert.fail
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.FlingInfo
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
-    private lateinit var underTest: FromLockscreenTransitionInteractor
-
-    // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
-    // interactor is provided to any classes that need it.
-    override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
-        Lazy {
-            underTest
+class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
         }
 
-    private lateinit var testComponent: TestComponent
-    @Mock private lateinit var activityManagerWrapper: ActivityManagerWrapper
-
-    private var topActivityClassName = "launcher"
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-        MockitoAnnotations.initMocks(this)
-
-        testComponent =
-            DaggerFromLockscreenTransitionInteractorTest_TestComponent.factory()
-                .create(
-                    test = this,
-                    mocks =
-                        TestMocksModule(
-                            activityManagerWrapper = activityManagerWrapper,
-                        ),
-                )
-        underTest = testComponent.underTest
-        testScope = testComponent.testScope
-        transitionRepository = testComponent.transitionRepository
-
-        activityManagerWrapper.mockTopActivityClassName(topActivityClassName)
-    }
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromLockscreenTransitionInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val shadeRepository = kosmos.fakeShadeRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
 
     @Test
-    fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+    fun testSurfaceBehindVisibility() =
         testScope.runTest {
             val values by collectValues(underTest.surfaceBehindVisibility)
             runCurrent()
 
             // Transition-specific surface visibility should be null ("don't care") initially.
-            assertEquals(
-                listOf(
-                    null,
-                ),
-                values
-            )
+            assertThat(values).containsExactly(null)
 
             transitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -107,15 +76,12 @@
                     to = KeyguardState.AOD,
                 )
             )
-
             runCurrent()
 
-            assertEquals(
-                listOf(
+            assertThat(values)
+                .containsExactly(
                     null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
-                ),
-                values
-            )
+                )
 
             transitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -124,159 +90,67 @@
                     to = KeyguardState.GONE,
                 )
             )
-
             runCurrent()
 
-            assertEquals(
-                listOf(
+            assertThat(values)
+                .containsExactly(
                     null,
                     true, // Surface is made visible immediately during LOCKSCREEN -> GONE
-                ),
-                values
+                )
+                .inOrder()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
             )
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    null,
+                    true, // Surface remains visible.
+                )
+                .inOrder()
         }
 
     @Test
-    fun testSurfaceBehindModel() =
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionsToGone_whenDismissFlingWhileDismissable_flagEnabled() =
         testScope.runTest {
-            val values by collectValues(underTest.surfaceBehindModel)
+            underTest.start()
+            verify(transitionRepository, never()).startTransition(any())
+
+            keyguardRepository.setKeyguardDismissible(true)
             runCurrent()
-
-            assertEquals(
-                values,
-                listOf(
-                    null, // We should start null ("don't care").
-                )
-            )
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                )
+            shadeRepository.setCurrentFling(
+                FlingInfo(expand = true) // Not a dismiss fling (expand = true).
             )
             runCurrent()
 
-            assertEquals(
-                listOf(
-                    null, // LOCKSCREEN -> AOD does not have specific view params.
-                ),
-                values
-            )
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
+            assertThatRepository(transitionRepository)
+                .startedTransition(
                     from = KeyguardState.LOCKSCREEN,
                     to = KeyguardState.GONE,
                 )
-            )
-            runCurrent()
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.RUNNING,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
-                    value = 0.01f,
-                )
-            )
-            runCurrent()
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.RUNNING,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
-                    value = 0.99f,
-                )
-            )
-            runCurrent()
-
-            assertEquals(3, values.size)
-            val model1percent = values[1]
-            val model99percent = values[2]
-
-            try {
-                // We should initially have an alpha of 0f when unlocking, so the surface is not
-                // visible
-                // while lockscreen UI animates out.
-                assertEquals(0f, model1percent!!.alpha)
-
-                // By the end it should probably be visible.
-                assertTrue(model99percent!!.alpha > 0f)
-            } catch (e: NullPointerException) {
-                fail("surfaceBehindModel was unexpectedly null.")
-            }
         }
 
     @Test
-    fun testSurfaceBehindModel_alpha1_whenTransitioningWithInWindowAnimation() =
+    @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testDoesNotTransitionToGone_whenDismissFlingWhileDismissable_flagDisabled() =
         testScope.runTest {
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
-                topActivityClassName
+            underTest.start()
+            verify(transitionRepository, never()).startTransition(any())
+
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            shadeRepository.setCurrentFling(
+                FlingInfo(expand = true) // Not a dismiss fling (expand = true).
             )
             runCurrent()
 
-            val values by collectValues(underTest.surfaceBehindModel)
-            runCurrent()
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
-                )
-            )
-            runCurrent()
-
-            assertEquals(1f, values[values.size - 1]?.alpha)
+            assertThatRepository(transitionRepository).noTransitionsStarted()
         }
-
-    @Test
-    fun testSurfaceBehindModel_alphaZero_whenNotTransitioningWithInWindowAnimation() =
-        testScope.runTest {
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
-                "not_launcher"
-            )
-            runCurrent()
-
-            val values by collectValues(underTest.surfaceBehindModel)
-            runCurrent()
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
-                )
-            )
-            runCurrent()
-
-            assertEquals(0f, values[values.size - 1]?.alpha)
-        }
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-            ]
-    )
-    interface TestComponent {
-        val underTest: FromLockscreenTransitionInteractor
-        val testScope: TestScope
-        val transitionRepository: FakeKeyguardTransitionRepository
-        val surfaceBehindRepository: FakeKeyguardSurfaceBehindRepository
-        val inWindowLauncherUnlockAnimationRepository: InWindowLauncherUnlockAnimationRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 6092b6b35..f33a5c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -18,61 +18,33 @@
 
 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.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertTrue
 import junit.framework.Assert.fail
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
-    private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
-
-    private val mSelectedUserInteractor = SelectedUserInteractor(FakeUserRepository())
-
-    // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
-    // underTest interactor is provided to any classes that need it.
-    override var fromPrimaryBouncerTransitionInteractorLazy:
-        Lazy<FromPrimaryBouncerTransitionInteractor>? =
-        Lazy {
-            underTest
-        }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-
-        underTest =
-            FromPrimaryBouncerTransitionInteractor(
-                transitionRepository = super.transitionRepository,
-                transitionInteractor = super.transitionInteractor,
-                scope = super.testScope.backgroundScope,
-                bgDispatcher = super.testDispatcher,
-                mainDispatcher = super.testDispatcher,
-                keyguardInteractor = super.keyguardInteractor,
-                communalInteractor = super.communalInteractor,
-                flags = FakeFeatureFlags(),
-                keyguardSecurityModel = mock(),
-                powerInteractor = PowerInteractorFactory.create().powerInteractor,
-                selectedUserInteractor = mSelectedUserInteractor
-            )
-    }
+class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
+    val testScope = kosmos.testScope
+    val selectedUserInteractor = kosmos.selectedUserInteractor
+    val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
     @Test
     fun testSurfaceBehindVisibility() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index e75f557..62855d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -21,16 +21,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -42,11 +41,12 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardDismissActionInteractorTest : SysuiTestCase() {
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    val kosmos = testKosmos()
 
-    private lateinit var dispatcher: TestDispatcher
-    private lateinit var testScope: TestScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    private val testScope = kosmos.testScope
 
     private lateinit var dismissInteractorWithDependencies:
         KeyguardDismissInteractorFactory.WithDependencies
@@ -55,25 +55,18 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        dispatcher = StandardTestDispatcher()
-        testScope = TestScope(dispatcher)
 
         dismissInteractorWithDependencies =
             KeyguardDismissInteractorFactory.create(
                 context = context,
                 testScope = testScope,
+                keyguardRepository = keyguardRepository,
             )
-        keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
-        transitionRepository = FakeKeyguardTransitionRepository()
 
         underTest =
             KeyguardDismissActionInteractor(
                 keyguardRepository,
-                KeyguardTransitionInteractorFactory.create(
-                        scope = testScope.backgroundScope,
-                        repository = transitionRepository,
-                    )
-                    .keyguardTransitionInteractor,
+                kosmos.keyguardTransitionInteractor,
                 dismissInteractorWithDependencies.interactor,
                 testScope.backgroundScope,
             )
@@ -180,7 +173,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
             assertThat(executeDismissAction).isNotNull()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index 71fcf6f..3f05bfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -20,148 +20,211 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.mockito.whenever
-import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.TestScope
+import com.android.systemui.keyguard.util.mockTopActivityClassName
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.assertValuesMatch
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.keyguardSurfaceBehindInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val inWindowUnlockInteractor = kosmos.inWindowLauncherUnlockAnimationInteractor
+    private val activityManagerWrapper = kosmos.activityManagerWrapper
 
-    private lateinit var underTest: KeyguardSurfaceBehindInteractor
-    private lateinit var repository: FakeKeyguardSurfaceBehindRepository
-
-    @Mock
-    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
-    @Mock
-    private lateinit var fromPrimaryBouncerTransitionInteractor:
-        FromPrimaryBouncerTransitionInteractor
-
-    private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
-    private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
-
-    private val testScope = TestScope()
-
-    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private val LAUNCHER_ACTIVITY_NAME = "launcher"
 
     @Before
     fun setUp() {
-        initMocks(this)
+        inWindowUnlockInteractor.setLauncherActivityClass(LAUNCHER_ACTIVITY_NAME)
 
-        whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
-            .thenReturn(flowOf(lockscreenSurfaceBehindModel))
-        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
-            .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
-
-        transitionRepository = FakeKeyguardTransitionRepository()
-
-        transitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = testScope.backgroundScope,
-                    repository = transitionRepository,
-                )
-                .keyguardTransitionInteractor
-
-        repository = FakeKeyguardSurfaceBehindRepository()
-        underTest =
-            KeyguardSurfaceBehindInteractor(
-                repository = repository,
-                fromLockscreenInteractor = fromLockscreenTransitionInteractor,
-                fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
-                transitionInteractor = transitionInteractor,
-            )
+        // Default to having something other than Launcher on top.
+        activityManagerWrapper.mockTopActivityClassName("not_launcher")
     }
 
     @Test
-    fun viewParamsSwitchToCorrectFlow() =
+    fun testSurfaceBehindModel_toAppSurface() =
         testScope.runTest {
             val values by collectValues(underTest.viewParams)
-
-            // Start on the LOCKSCREEN.
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.LOCKSCREEN,
-                )
-            )
-
             runCurrent()
 
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.FINISHED,
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.LOCKSCREEN,
+            assertThat(values)
+                .containsExactly(
+                    // We're initialized in LOCKSCREEN.
+                    KeyguardSurfaceBehindModel(alpha = 0f),
                 )
-            )
-
-            runCurrent()
-
-            // We're on LOCKSCREEN; we should be using the default params.
-            assertEquals(1, values.size)
-            assertTrue(values[0].alpha == 0f)
 
             transitionRepository.sendTransitionStep(
                 TransitionStep(
-                    transitionState = TransitionState.STARTED,
                     from = KeyguardState.LOCKSCREEN,
                     to = KeyguardState.GONE,
-                )
-            )
-
-            runCurrent()
-
-            // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
-            // surface behind model.
-            assertEquals(2, values.size)
-            assertEquals(values[1], lockscreenSurfaceBehindModel)
-
-            transitionRepository.sendTransitionStep(
-                TransitionStep(
                     transitionState = TransitionState.STARTED,
-                    from = KeyguardState.PRIMARY_BOUNCER,
-                    to = KeyguardState.GONE,
                 )
             )
-
             runCurrent()
 
-            // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
-            // surface behind model.
-            assertEquals(3, values.size)
-            assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+            values.assertValuesMatch(
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+                // Once we start a transition to GONE, we should fade in and translate up. The exact
+                // start value depends on screen density, so just look for != 0.
+                {
+                    it.animateFromAlpha == 0f &&
+                        it.alpha == 1f &&
+                        it.animateFromTranslationY != 0f &&
+                        it.translationY == 0f
+                }
+            )
 
             transitionRepository.sendTransitionStep(
                 TransitionStep(
-                    transitionState = TransitionState.FINISHED,
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.LOCKSCREEN,
                     to = KeyguardState.GONE,
+                    transitionState = TransitionState.RUNNING,
                 )
             )
-
             runCurrent()
 
-            // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
-            // alpha=1f when we're GONE.
-            assertEquals(4, values.size)
-            assertEquals(1f, values[3].alpha)
+            values.assertValuesMatch(
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+                // There should be no change as we're RUNNING.
+                {
+                    it.animateFromAlpha == 0f &&
+                        it.alpha == 1f &&
+                        it.animateFromTranslationY != 0f &&
+                        it.translationY == 0f
+                }
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            runCurrent()
+
+            values.assertValuesMatch(
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+                {
+                    it.animateFromAlpha == 0f &&
+                        it.alpha == 1f &&
+                        it.animateFromTranslationY != 0f &&
+                        it.translationY == 0f
+                },
+                // Once the current state is GONE, we should default to alpha = 1f.
+                { it == KeyguardSurfaceBehindModel(alpha = 1f) }
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel_toLauncher() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+            activityManagerWrapper.mockTopActivityClassName(LAUNCHER_ACTIVITY_NAME)
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    // We're initialized in LOCKSCREEN.
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                )
+                .inOrder()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                    // We should instantly set alpha = 1, with no animations, when Launcher is
+                    // behind
+                    // the keyguard since we're playing in-window animations.
+                    KeyguardSurfaceBehindModel(alpha = 1f),
+                )
+                .inOrder()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.RUNNING,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            runCurrent()
+
+            assertThat(values)
+                .containsExactly(
+                    KeyguardSurfaceBehindModel(alpha = 0f),
+                    // Should have remained at alpha = 1f through the entire animation.
+                    KeyguardSurfaceBehindModel(alpha = 1f),
+                )
+                .inOrder()
+        }
+
+    @Test
+    fun testSurfaceBehindModel_fromNotificationLaunch() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+            runCurrent()
+
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.RUNNING,
+                    value = 0.5f,
+                )
+            )
+            runCurrent()
+
+            values.assertValuesMatch(
+                // We should be at alpha = 0f during the animation.
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+            )
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
deleted file mode 100644
index a03aed0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
+++ /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.systemui.keyguard.domain.interactor
-
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-
-open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    val testDispatcher = StandardTestDispatcher()
-    var testScope = TestScope(testDispatcher)
-
-    lateinit var keyguardRepository: FakeKeyguardRepository
-    lateinit var transitionRepository: FakeKeyguardTransitionRepository
-
-    lateinit var keyguardInteractor: KeyguardInteractor
-    lateinit var communalInteractor: CommunalInteractor
-    lateinit var transitionInteractor: KeyguardTransitionInteractor
-
-    /**
-     * Replace these lazy providers with non-null ones if you want test dependencies to use a real
-     * instance of the interactor for the test.
-     */
-    open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
-        null
-    open var fromPrimaryBouncerTransitionInteractorLazy:
-        Lazy<FromPrimaryBouncerTransitionInteractor>? =
-        null
-
-    open fun setUp() {
-        keyguardRepository = FakeKeyguardRepository()
-        transitionRepository = FakeKeyguardTransitionRepository()
-
-        keyguardInteractor =
-            KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
-
-        communalInteractor = kosmos.communalInteractor
-
-        transitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    repository = transitionRepository,
-                    keyguardInteractor = keyguardInteractor,
-                    scope = testScope.backgroundScope,
-                    fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
-                            ?: Lazy { mock() },
-                    fromPrimaryBouncerTransitionInteractor =
-                        fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
-                )
-                .also {
-                    fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
-                    fromPrimaryBouncerTransitionInteractorLazy =
-                        it.fromPrimaryBouncerTransitionInteractor
-                }
-                .keyguardTransitionInteractor
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 2812718..5b93df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,18 +23,13 @@
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -42,29 +37,26 @@
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -89,22 +81,26 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardTransitionScenariosTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply {
+            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+            this.commandQueue = fakeCommandQueue
+        }
     private val testScope = kosmos.testScope
 
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
-    private lateinit var commandQueue: FakeCommandQueue
-    private lateinit var shadeRepository: FakeShadeRepository
-    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private var commandQueue = kosmos.fakeCommandQueue
+    private val shadeRepository = kosmos.fakeShadeRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val transitionInteractor = kosmos.keyguardTransitionInteractor
     private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
-    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
     private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
     private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
     private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
@@ -112,48 +108,26 @@
     private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
     private lateinit var fromAlternateBouncerTransitionInteractor:
         FromAlternateBouncerTransitionInteractor
-    private lateinit var fromPrimaryBouncerTransitionInteractor:
-        FromPrimaryBouncerTransitionInteractor
+    private val fromPrimaryBouncerTransitionInteractor =
+        kosmos.fromPrimaryBouncerTransitionInteractor
     private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
         FromDreamingLockscreenHostedTransitionInteractor
     private lateinit var fromGlanceableHubTransitionInteractor:
         FromGlanceableHubTransitionInteractor
 
-    private lateinit var powerInteractor: PowerInteractor
-    private lateinit var keyguardInteractor: KeyguardInteractor
-    private lateinit var communalInteractor: CommunalInteractor
+    private val powerInteractor = kosmos.powerInteractor
+    private val keyguardInteractor = kosmos.keyguardInteractor
+    private val communalInteractor = kosmos.communalInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        keyguardRepository = kosmos.fakeKeyguardRepository
-        bouncerRepository = kosmos.fakeKeyguardBouncerRepository
-        commandQueue = FakeCommandQueue()
-        shadeRepository = kosmos.fakeShadeRepository
-        transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository)
-        powerInteractor = PowerInteractorFactory.create().powerInteractor
-        communalInteractor = kosmos.communalInteractor
-
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
         featureFlags = FakeFeatureFlags()
 
-        keyguardInteractor = createKeyguardInteractor()
-
-        transitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = testScope,
-                    repository = transitionRepository,
-                    keyguardInteractor = keyguardInteractor,
-                    fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
-                    fromPrimaryBouncerTransitionInteractor = {
-                        fromPrimaryBouncerTransitionInteractor
-                    },
-                )
-                .keyguardTransitionInteractor
-
         val glanceableHubTransitions =
             GlanceableHubTransitions(
                 scope = testScope,
@@ -162,45 +136,9 @@
                 transitionRepository = transitionRepository,
                 communalInteractor = communalInteractor
             )
-        fromLockscreenTransitionInteractor =
-            FromLockscreenTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    flags = featureFlags,
-                    shadeRepository = shadeRepository,
-                    powerInteractor = powerInteractor,
-                    glanceableHubTransitions = glanceableHubTransitions,
-                    inWindowLauncherUnlockAnimationInteractor = {
-                        InWindowLauncherUnlockAnimationInteractor(
-                            InWindowLauncherUnlockAnimationRepository(),
-                            testScope,
-                            transitionInteractor,
-                            { FakeKeyguardSurfaceBehindRepository() },
-                            mock(),
-                        )
-                    },
-                )
-                .apply { start() }
 
-        fromPrimaryBouncerTransitionInteractor =
-            FromPrimaryBouncerTransitionInteractor(
-                    scope = testScope,
-                    bgDispatcher = kosmos.testDispatcher,
-                    mainDispatcher = kosmos.testDispatcher,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    flags = featureFlags,
-                    keyguardSecurityModel = keyguardSecurityModel,
-                    powerInteractor = powerInteractor,
-                    communalInteractor = communalInteractor,
-                    selectedUserInteractor = mSelectedUserInteractor,
-                )
-                .apply { start() }
+        fromLockscreenTransitionInteractor.start()
+        fromPrimaryBouncerTransitionInteractor.start()
 
         fromDreamingTransitionInteractor =
             FromDreamingTransitionInteractor(
@@ -315,15 +253,13 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -342,15 +278,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.OCCLUDED,
+                    ownerName = "FromOccludedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -369,15 +303,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.OCCLUDED,
+                    ownerName = "FromOccludedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -397,17 +329,15 @@
 
             // WHEN the device begins to dream
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -428,17 +358,15 @@
             // WHEN the device begins to dream and the dream is lockscreen hosted
             keyguardRepository.setDreamingWithOverlay(true)
             keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -457,15 +385,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -484,15 +410,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -515,17 +439,15 @@
 
             // WHEN the lockscreen hosted dream stops
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to Lockscreen should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -545,15 +467,13 @@
             )
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to Gone should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GONE,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -575,15 +495,13 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -608,15 +526,13 @@
             )
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -640,15 +556,13 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.OCCLUDED,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -664,15 +578,13 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -710,7 +622,7 @@
 
             // WHEN a signal comes that dreaming is enabled
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
             // THEN the transition is ignored
             verify(transitionRepository, never()).startTransition(any())
@@ -728,15 +640,13 @@
             keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GONE,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -760,15 +670,13 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    from = KeyguardState.DOZING,
+                    ownerName = FromDozingTransitionInteractor::class.simpleName,
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -787,15 +695,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -814,15 +720,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -837,15 +741,13 @@
             keyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -865,17 +767,15 @@
 
             // WHEN the device begins to dream
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -896,17 +796,15 @@
             // WHEN the device begins to dream with the lockscreen hosted dream
             keyguardRepository.setDreamingWithOverlay(true)
             keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -929,15 +827,13 @@
             keyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    from = KeyguardState.GONE,
+                    ownerName = FromGoneTransitionInteractor::class.simpleName,
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -955,21 +851,19 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
 
     @Test
-    fun alternateBoucnerToAod() =
+    fun alternateBouncerToAod() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
@@ -985,17 +879,15 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1018,17 +910,15 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1048,17 +938,15 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1086,18 +974,16 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1117,15 +1003,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.AOD,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1145,15 +1030,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.DOZING,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1169,15 +1053,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1201,16 +1084,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1232,15 +1113,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1263,15 +1143,14 @@
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to GONE should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.GONE,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1292,15 +1171,14 @@
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1329,15 +1207,14 @@
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to GLANCEABLE_HUB should occur
-            assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromOccludedTransitionInteractor::class.simpleName,
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1354,15 +1231,14 @@
             bouncerRepository.setAlternateVisible(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AlternateBouncer should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1379,15 +1255,14 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AlternateBouncer should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1405,15 +1280,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1430,15 +1304,14 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromDozingTransitionInteractor",
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1457,15 +1330,14 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromDreamingTransitionInteractor",
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1487,15 +1359,14 @@
             )
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromDreamingTransitionInteractor",
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.AOD,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1511,15 +1382,14 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1535,15 +1405,14 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.AOD)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromAodTransitionInteractor",
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1559,15 +1428,14 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.AOD)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromAodTransitionInteractor",
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1593,14 +1461,13 @@
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => OCCLUDED should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1620,14 +1487,13 @@
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNull() // dragging should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNull() }, // dragging should be manually animated
+                )
 
             // WHEN the user stops dragging and shade is back to expanded
             clearInvocations(transitionRepository)
@@ -1636,14 +1502,13 @@
             shadeRepository.setLegacyShadeExpansion(1f)
             runCurrent()
 
-            // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
-            val info2 =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info2.animator).isNotNull()
+            // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1675,15 +1540,13 @@
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName)
-                .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
 
             // WHEN the user stops dragging and the glanceable hub opening is cancelled
             clearInvocations(transitionRepository)
@@ -1695,14 +1558,13 @@
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
 
-            // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
-            val info2 =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.LOCKSCREEN,
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1733,16 +1595,13 @@
             progress.value = .1f
             runCurrent()
 
-            // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
 
             // WHEN the user stops dragging and the glanceable hub closing is cancelled
             clearInvocations(transitionRepository)
@@ -1754,14 +1613,11 @@
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
 
-            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
-            val info2 =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1776,16 +1632,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DOZING,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1800,16 +1653,13 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1824,16 +1674,13 @@
             bouncerRepository.setAlternateVisible(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1857,16 +1704,13 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1881,16 +1725,13 @@
             keyguardRepository.setKeyguardGoingAway(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.GONE,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1910,33 +1751,19 @@
 
             // WHEN the device begins to dream
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DREAMING,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
 
-    private fun createKeyguardInteractor(): KeyguardInteractor {
-        return KeyguardInteractorFactory.create(
-                featureFlags = featureFlags,
-                repository = keyguardRepository,
-                commandQueue = commandQueue,
-                bouncerRepository = bouncerRepository,
-                powerInteractor = powerInteractor,
-            )
-            .keyguardInteractor
-    }
-
     private suspend fun TestScope.runTransitionAndSetWakefulness(
         from: KeyguardState,
         to: KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.kt
new file mode 100644
index 0000000..d77519d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorTest.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.systemui.keyguard.domain.interactor
+
+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.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.FlingInfo
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class SwipeToDismissInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.swipeToDismissInteractor
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val shadeRepository = kosmos.fakeShadeRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+
+    @Test
+    fun testDismissFling_emitsInLockscreenWhenDismissable() =
+        testScope.runTest {
+            val values by collectValues(underTest.dismissFling)
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            shadeRepository.setCurrentFling(FlingInfo(expand = false))
+            runCurrent()
+
+            assertThat(values)
+                .comparingElementsUsing(
+                    Correspondence.transforming(
+                        { fling: FlingInfo? -> fling?.expand },
+                        "expand equals"
+                    )
+                )
+                .containsExactly(null, false)
+                .inOrder()
+        }
+
+    @Test
+    fun testDismissFling_doesNotEmitInWrongStateOrNotDismissable() =
+        testScope.runTest {
+            val values by collectValues(underTest.dismissFling)
+            keyguardRepository.setKeyguardDismissible(false)
+            shadeRepository.setCurrentFling(FlingInfo(expand = false))
+            runCurrent()
+
+            assertThat(values).containsExactly(null)
+
+            // Not in LOCKSCREEN, but dismissable.
+            transitionRepository.sendTransitionSteps(
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.GONE,
+                testScope
+            )
+            keyguardRepository.setKeyguardDismissible(true)
+
+            // Re-emit a valid dismiss fling.
+            shadeRepository.setCurrentFling(null)
+            shadeRepository.setCurrentFling(FlingInfo(expand = false))
+            runCurrent()
+
+            assertThat(values).containsExactly(null)
+        }
+
+    @Test
+    fun testExpandFling_doesNotEmit() =
+        testScope.runTest {
+            val values by collectValues(underTest.dismissFling)
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            shadeRepository.setCurrentFling(
+                FlingInfo(expand = true) // Not a dismiss fling (expand = true).
+            )
+            runCurrent()
+
+            assertThat(values).containsExactly(null)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6eed427..a4483bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -20,76 +20,46 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
-
-    private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
-
-    @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
-    @Mock
-    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
-    @Mock
-    private lateinit var fromPrimaryBouncerTransitionInteractor:
-        FromPrimaryBouncerTransitionInteractor
-
     private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
     private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
     private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
 
-    private val testScope = TestScope()
+    private val kosmos =
+        testKosmos().apply {
+            fromLockscreenTransitionInteractor = mock<FromLockscreenTransitionInteractor>()
+            fromPrimaryBouncerTransitionInteractor = mock<FromPrimaryBouncerTransitionInteractor>()
+            keyguardSurfaceBehindInteractor = mock<KeyguardSurfaceBehindInteractor>()
 
-    private lateinit var keyguardInteractor: KeyguardInteractor
-    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+            whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+                .thenReturn(lockscreenSurfaceVisibilityFlow)
+            whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+                .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+            whenever(keyguardSurfaceBehindInteractor.isAnimatingSurface)
+                .thenReturn(surfaceBehindIsAnimatingFlow)
+        }
 
-    @Before
-    fun setUp() {
-        initMocks(this)
-
-        whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
-            .thenReturn(lockscreenSurfaceVisibilityFlow)
-        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
-            .thenReturn(primaryBouncerSurfaceVisibilityFlow)
-        whenever(surfaceBehindInteractor.isAnimatingSurface)
-            .thenReturn(surfaceBehindIsAnimatingFlow)
-
-        transitionRepository = FakeKeyguardTransitionRepository()
-
-        transitionInteractor =
-            KeyguardTransitionInteractorFactory.create(
-                    scope = testScope.backgroundScope,
-                    repository = transitionRepository,
-                )
-                .also { keyguardInteractor = it.keyguardInteractor }
-                .keyguardTransitionInteractor
-
-        underTest =
-            WindowManagerLockscreenVisibilityInteractor(
-                keyguardInteractor = keyguardInteractor,
-                transitionInteractor = transitionInteractor,
-                surfaceBehindInteractor = surfaceBehindInteractor,
-                fromLockscreenTransitionInteractor,
-                fromPrimaryBouncerTransitionInteractor,
-            )
-    }
+    private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor
+    private val testScope = kosmos.testScope
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
     @Test
     fun surfaceBehindVisibility_switchesToCorrectFlow() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index af38523c..90943de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -46,7 +46,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -67,7 +67,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.max
 import kotlin.math.min
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -83,7 +82,6 @@
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@@ -208,11 +206,7 @@
             KeyguardLongPressInteractor(
                 appContext = mContext,
                 scope = testScope.backgroundScope,
-                transitionInteractor =
-                    KeyguardTransitionInteractorFactory.create(
-                            scope = TestScope().backgroundScope,
-                        )
-                        .keyguardTransitionInteractor,
+                transitionInteractor = kosmos.keyguardTransitionInteractor,
                 repository = repository,
                 logger = UiEventLoggerFake(),
                 featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
new file mode 100644
index 0000000..655a551
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.util
+
+import androidx.core.animation.ValueAnimator
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertAbout
+import junit.framework.Assert.assertEquals
+import kotlin.test.fail
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */
+class KeyguardTransitionRepositorySpySubject
+private constructor(
+    failureMetadata: FailureMetadata,
+    private val repository: KeyguardTransitionRepository,
+) : Subject(failureMetadata, repository) {
+
+    /**
+     * Asserts that we started a transition to the given state, optionally checking additional
+     * parameters. If an animator param or assertion is not provided, we will not assert anything
+     * about the animator.
+     */
+    fun startedTransition(
+        ownerName: String? = null,
+        from: KeyguardState? = null,
+        to: KeyguardState,
+        modeOnCanceled: TransitionModeOnCanceled? = null,
+    ) {
+        startedTransition(ownerName, from, to, {}, modeOnCanceled)
+    }
+
+    /**
+     * Asserts that we started a transition to the given state, optionally verifying additional
+     * params.
+     */
+    fun startedTransition(
+        ownerName: String? = null,
+        from: KeyguardState? = null,
+        to: KeyguardState,
+        animator: ValueAnimator?,
+        modeOnCanceled: TransitionModeOnCanceled? = null,
+    ) {
+        startedTransition(ownerName, from, to, { assertEquals(animator, it) }, modeOnCanceled)
+    }
+
+    /**
+     * Asserts that we started a transition to the given state, optionally verifying additional
+     * params.
+     */
+    fun startedTransition(
+        ownerName: String? = null,
+        from: KeyguardState? = null,
+        to: KeyguardState,
+        animatorAssertion: (Subject) -> Unit,
+        modeOnCanceled: TransitionModeOnCanceled? = null,
+    ) {
+        withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) }
+            .also { transitionInfo ->
+                assertEquals(to, transitionInfo.to)
+                animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator))
+                from?.let { assertEquals(it, transitionInfo.from) }
+                ownerName?.let { assertEquals(it, transitionInfo.ownerName) }
+                modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) }
+            }
+    }
+
+    /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */
+    fun noTransitionsStarted() {
+        verify(repository, never()).startTransition(any())
+    }
+
+    companion object {
+        fun assertThat(
+            repository: KeyguardTransitionRepository
+        ): KeyguardTransitionRepositorySpySubject =
+            assertAbout { failureMetadata, repository: KeyguardTransitionRepository ->
+                    if (!Mockito.mockingDetails(repository).isSpy) {
+                        fail(
+                            "Cannot assert on a non-spy KeyguardTransitionRepository. " +
+                                "Use Mockito.spy(keyguardTransitionRepository)."
+                        )
+                    }
+                    KeyguardTransitionRepositorySpySubject(failureMetadata, repository)
+                }
+                .that(repository)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 043dae6..100e579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -24,9 +24,9 @@
 import android.widget.SeekBar
 import android.widget.TextView
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -86,7 +86,7 @@
     fun seekBarGone() {
         // WHEN seek bar is disabled
         val isEnabled = false
-        val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0)
+        val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0, false)
         observer.onChanged(data)
         // THEN seek bar shows just a thin line with no text
         assertThat(seekBarView.isEnabled()).isFalse()
@@ -99,7 +99,7 @@
     fun seekBarVisible() {
         // WHEN seek bar is enabled
         val isEnabled = true
-        val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000, true)
         observer.onChanged(data)
         // THEN seek bar is visible and thick
         assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
@@ -109,7 +109,7 @@
     @Test
     fun seekBarProgress() {
         // WHEN part of the track has been played
-        val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
         observer.onChanged(data)
         // THEN seek bar shows the progress
         assertThat(seekBarView.progress).isEqualTo(3000)
@@ -123,7 +123,8 @@
     fun seekBarDisabledWhenSeekNotAvailable() {
         // WHEN seek is not available
         val isSeekAvailable = false
-        val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+        val data =
+            SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
         observer.onChanged(data)
         // THEN seek bar is not enabled
         assertThat(seekBarView.isEnabled()).isFalse()
@@ -133,7 +134,8 @@
     fun seekBarEnabledWhenSeekNotAvailable() {
         // WHEN seek is available
         val isSeekAvailable = true
-        val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+        val data =
+            SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
         observer.onChanged(data)
         // THEN seek bar is not enabled
         assertThat(seekBarView.isEnabled()).isTrue()
@@ -144,7 +146,7 @@
         // WHEN playing
         val isPlaying = true
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is animating
         verify(mockSquigglyProgress).animate = true
@@ -155,7 +157,7 @@
         // WHEN not playing & not scrubbing
         val isPlaying = false
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is not animating
         verify(mockSquigglyProgress).animate = false
@@ -166,7 +168,7 @@
         // WHEN playing & scrubbing
         val isPlaying = true
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is not animating
         verify(mockSquigglyProgress).animate = false
@@ -177,7 +179,7 @@
         // WHEN playing & scrubbing
         val isPlaying = false
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is not animating
         verify(mockSquigglyProgress).animate = false
@@ -187,7 +189,7 @@
     fun seekBarProgress_enabledAndScrubbing_timeViewsHaveTime() {
         val isEnabled = true
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
 
         observer.onChanged(data)
 
@@ -199,7 +201,7 @@
     fun seekBarProgress_disabledAndScrubbing_timeViewsEmpty() {
         val isEnabled = false
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
 
         observer.onChanged(data)
 
@@ -211,7 +213,7 @@
     fun seekBarProgress_enabledAndNotScrubbing_timeViewsEmpty() {
         val isEnabled = true
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
 
         observer.onChanged(data)
 
@@ -221,8 +223,8 @@
 
     @Test
     fun seekBarJumpAnimation() {
-        val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000)
-        val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000)
+        val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000, true)
+        val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000, true)
 
         // Set initial position of progress bar
         observer.onChanged(data0)
@@ -241,7 +243,7 @@
         observer.animationEnabled = false
         val isPlaying = true
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
 
         // THEN progress drawable does not animate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 437a35f..e3c4c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -475,10 +475,7 @@
 
         // Then the device name is the PhoneMediaDevice string
         val data = captureDeviceData(KEY)
-        assertThat(data.name)
-            .isEqualTo(
-                context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
-            )
+        assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 310e0b8..f3b9102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,13 +30,12 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
@@ -53,6 +52,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -93,6 +93,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
 class MediaCarouselControllerTest : SysuiTestCase() {
+    val kosmos = testKosmos()
 
     @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
     @Mock lateinit var panel: MediaControlPanel
@@ -115,7 +116,7 @@
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var globalSettings: GlobalSettings
-    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
     @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
     @Captor
     lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -132,7 +133,6 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
-        transitionRepository = FakeKeyguardTransitionRepository()
         bgExecutor = FakeExecutor(clock)
         mediaCarouselController =
             MediaCarouselController(
@@ -152,11 +152,7 @@
                 debugLogger,
                 mediaFlags,
                 keyguardUpdateMonitor,
-                KeyguardTransitionInteractorFactory.create(
-                        scope = TestScopeProvider.getTestScope().backgroundScope,
-                        repository = transitionRepository,
-                    )
-                    .keyguardTransitionInteractor,
+                kosmos.keyguardTransitionInteractor,
                 globalSettings
             )
         verify(configurationController).addCallback(capture(configListener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 239bf65..87d093f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
@@ -52,8 +53,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertNotNull
@@ -106,8 +105,7 @@
     private lateinit var dreamOverlayCallback:
         ArgumentCaptor<(DreamOverlayStateController.Callback)>
     @JvmField @Rule val mockito = MockitoJUnit.rule()
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val testScope = kosmos.testScope
     private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
     private lateinit var mediaFrame: ViewGroup
@@ -532,58 +530,6 @@
         }
 
     @Test
-    fun testCommunalLocation_showsOverLockscreen() =
-        testScope.runTest {
-            // Device is on lock screen.
-            whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-
-            // UMO goes to communal even over the lock screen.
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-            runCurrent()
-            verify(mediaCarouselController)
-                .onDesiredLocationChanged(
-                    eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
-                    nullable(),
-                    eq(false),
-                    anyLong(),
-                    anyLong()
-                )
-        }
-
-    @Test
-    fun testCommunalLocation_showsUntilQsExpands() =
-        testScope.runTest {
-            // Device is on lock screen.
-            whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-            runCurrent()
-            verify(mediaCarouselController)
-                .onDesiredLocationChanged(
-                    eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
-                    nullable(),
-                    eq(false),
-                    anyLong(),
-                    anyLong()
-                )
-            clearInvocations(mediaCarouselController)
-
-            // Start opening the shade.
-            mediaHierarchyManager.qsExpansion = 0.1f
-            runCurrent()
-
-            // UMO goes to the shade instead.
-            verify(mediaCarouselController)
-                .onDesiredLocationChanged(
-                    eq(MediaHierarchyManager.LOCATION_QS),
-                    any(MediaHostState::class.java),
-                    eq(false),
-                    anyLong(),
-                    anyLong()
-                )
-        }
-
-    @Test
     fun testQsExpandedChanged_noQqsMedia() {
         // When we are looking at QQS with active media
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 0000000..e044eec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+
+    private lateinit var dialog: AlertDialog
+
+    private val flags = mock<FeatureFlagsClassic>()
+    private val onStartRecordingClicked = mock<Runnable>()
+    private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+
+    private val mediaProjectionConfig: MediaProjectionConfig =
+        MediaProjectionConfig.createConfigForDefaultDisplay()
+    private val appName: String = "testApp"
+    private val hostUid: Int = 12345
+
+    private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+    private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+    private val resIdSingleAppDisabled =
+        R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+    @Before
+    fun setUp() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+    }
+
+    @After
+    fun teardown() {
+        if (::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+
+    @Test
+    fun showDialog_forceShowPartialScreenShareFalse() {
+        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+        // overrideDisableSingleAppOption = false
+        val overrideDisableSingleAppOption = false
+        setUpAndShowDialog(overrideDisableSingleAppOption)
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text2)
+                ?.text
+
+        // check that the first option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+        // check that the second option is single app and disabled
+        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+    }
+
+    @Test
+    fun showDialog_forceShowPartialScreenShareTrue() {
+        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+        // overrideDisableSingleAppOption = true
+        val overrideDisableSingleAppOption = true
+        setUpAndShowDialog(overrideDisableSingleAppOption)
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text1)
+                ?.text
+
+        // check that the first option is single app and enabled
+        assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+        // check that the second option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), secondOptionText)
+    }
+
+    private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+        val delegate =
+            MediaProjectionPermissionDialogDelegate(
+                context,
+                mediaProjectionConfig,
+                {},
+                onStartRecordingClicked,
+                appName,
+                overrideDisableSingleAppOption,
+                hostUid,
+                mediaProjectionMetricsLogger
+            )
+
+        dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+        SystemUIDialog.applyFlags(dialog)
+        SystemUIDialog.setDialogSize(dialog)
+
+        dialog.window?.addSystemFlags(
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+        )
+
+        delegate.onCreate(dialog, savedInstanceState = null)
+        dialog.show()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 28d35ce..e5ba569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -41,7 +41,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -57,8 +56,6 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import dagger.Lazy;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -70,6 +67,8 @@
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
+import dagger.Lazy;
+
 /**
  * Tests for {@link NavBarHelper}.
  */
@@ -271,8 +270,7 @@
     @Test
     public void initNavBarHelper_buttonModeNavBar_a11yButtonClickableState() {
         when(mAccessibilityManager.getAccessibilityShortcutTargets(
-                ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
-                createFakeShortcutTargets());
+                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
 
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -297,8 +295,7 @@
                 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
 
         when(mAccessibilityManager.getAccessibilityShortcutTargets(
-                ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
-                createFakeShortcutTargets());
+                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
         mAccessibilityServicesStateChangeListener.onAccessibilityServicesStateChanged(
                 mAccessibilityManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index db5bd9b..0d1e874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -182,6 +182,8 @@
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
+    private NavBarButtonClickLogger mNavBarButtonClickLogger;
+    @Mock
     private ViewTreeObserver mViewTreeObserver;
     NavBarHelper mNavBarHelper;
     @Mock
@@ -596,7 +598,8 @@
                 mUserContextProvider,
                 mWakefulnessLifecycle,
                 mTaskStackChangeListeners,
-                new FakeDisplayTracker(mContext)));
+                new FakeDisplayTracker(mContext),
+                mNavBarButtonClickLogger));
     }
 
     private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index e8aa8f0..bbae0c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -34,7 +34,7 @@
 import org.junit.Rule
 import org.junit.runner.RunWith
 
-/** Test for regression b/311121830 */
+/** Test for regression b/311121830 and b/323125376 */
 @RunWith(AndroidTestingRunner::class)
 @UiThreadTest
 @SmallTest
@@ -82,6 +82,55 @@
         assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
     }
 
+    @Test
+    fun alwaysLastIcon_twoStateChanges() {
+        // Need to inflate with the correct theme so the colors can be retrieved and the animations
+        // are run
+        val iconView =
+            AnimateQSIconViewImpl(
+                ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+            )
+
+        val initialState =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4])
+            }
+        val firstState =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+            }
+        val secondState =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[3])
+            }
+        val thirdState =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_NETWORK)
+            }
+
+        // Start with the initial state
+        iconView.setIcon(initialState, /* allowAnimations= */ false)
+
+        // Set the first state to animate, and advance time to one third of the animation
+        iconView.setIcon(firstState, /* allowAnimations= */ true)
+        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 3)
+
+        // Set the second state to animate and advance time by another third of animations length
+        iconView.setIcon(secondState, /* allowAnimations= */ true)
+        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 3)
+
+        // 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)
+
+        assertThat(iconView.mLastIcon).isEqualTo(thirdState.icon)
+    }
+
     private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) {
         override fun createIcon(): View {
             return object : ImageView(context) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 9ce77e5..deecc5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -166,7 +166,7 @@
 
     @Test
     public void testLogStopFromNotificationIntent() {
-        Intent stopIntent = RecordingService.getNotificationIntent(mContext);
+        Intent stopIntent = mRecordingService.getNotificationIntent(mContext);
         mRecordingService.onStartCommand(stopIntent, 0, 0);
 
         // Verify that we log the correct event
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 707a297..d757d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -202,7 +202,7 @@
     @Test
     fun testSeekBarTrackingStarted() {
         whenever(brightnessSliderView.value).thenReturn(42)
-        val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
+        val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
@@ -220,7 +220,7 @@
     @Test
     fun testSeekBarTrackingStopped() {
         whenever(brightnessSliderView.value).thenReturn(23)
-        val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
+        val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
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 a6e240b..9517f82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.shade
 
+import android.content.Context
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.MotionEvent
 import android.view.View
+import android.view.WindowManager
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -33,13 +35,18 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 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.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Assume.assumeTrue
 import org.junit.Before
@@ -51,11 +58,17 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@ExperimentalCoroutinesApi
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class GlanceableHubContainerControllerTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos: Kosmos =
+        testKosmos().apply {
+            // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using
+            // SharedFlow
+            testDispatcher = UnconfinedTestDispatcher()
+        }
 
     @Mock private lateinit var communalViewModel: CommunalViewModel
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -102,17 +115,24 @@
             R.dimen.communal_bottom_edge_swipe_region_height,
             BOTTOM_SWIPE_REGION_WIDTH
         )
+
+        initAndAttachContainerView()
+    }
+
+    @After
+    fun tearDown() {
+        ViewUtils.detachView(parentView)
     }
 
     @Test
-    fun isEnabled_interactorEnabled_interceptsTouches() {
+    fun isEnabled_communalEnabled_returnsTrue() {
         communalRepository.setIsCommunalEnabled(true)
 
         assertThat(underTest.isEnabled()).isTrue()
     }
 
     @Test
-    fun isEnabled_interactorDisabled_doesNotIntercept() {
+    fun isEnabled_communalDisabled_returnsFalse() {
         communalRepository.setIsCommunalEnabled(false)
 
         assertThat(underTest.isEnabled()).isFalse()
@@ -122,11 +142,29 @@
     fun initView_notEnabled_throwsException() {
         communalRepository.setIsCommunalEnabled(false)
 
+        underTest =
+            GlanceableHubContainerController(
+                communalInteractor,
+                communalViewModel,
+                keyguardTransitionInteractor,
+                shadeInteractor,
+                powerManager,
+            )
+
         assertThrows(RuntimeException::class.java) { underTest.initView(context) }
     }
 
     @Test
     fun initView_calledTwice_throwsException() {
+        underTest =
+            GlanceableHubContainerController(
+                communalInteractor,
+                communalViewModel,
+                keyguardTransitionInteractor,
+                shadeInteractor,
+                powerManager,
+            )
+
         // First call succeeds.
         underTest.initView(context)
 
@@ -135,25 +173,20 @@
     }
 
     @Test
-    fun onTouchEvent_touchInsideGestureRegion_interceptsTouches() {
-        // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+    fun onTouchEvent_communalClosed_doesNotIntercept() {
+        // Communal is closed.
+        goToScene(CommunalSceneKey.Blank)
 
-        initAndAttachContainerView()
-
-        // Touch events are intercepted.
-        assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
     }
 
     @Test
-    fun onTouchEvent_subsequentTouchesAfterGestureStart_interceptsTouches() {
-        // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+    fun onTouchEvent_openGesture_interceptsTouches() {
+        // Communal is closed.
+        goToScene(CommunalSceneKey.Blank)
 
-        initAndAttachContainerView()
-
-        // Initial touch down is intercepted, and so are touches outside of the region, until an up
-        // event is received.
+        // Initial touch down is intercepted, and so are touches outside of the region, until an
+        // up event is received.
         assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
         assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
         assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
@@ -163,34 +196,27 @@
     @Test
     fun onTouchEvent_communalOpen_interceptsTouches() {
         // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+        goToScene(CommunalSceneKey.Communal)
 
-        initAndAttachContainerView()
-        testableLooper.processAllMessages()
-
-        // Touch events are intercepted.
+        // Touch events are intercepted outside of any gesture areas.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
         // User activity sent to PowerManager.
         verify(powerManager).userActivity(any(), any(), any())
     }
 
     @Test
-    fun onTouchEvent_topSwipeWhenHubOpen_returnsFalse() {
+    fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() {
         // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
-        initAndAttachContainerView()
+        goToScene(CommunalSceneKey.Communal)
 
         // Touch event in the top swipe reqgion is not intercepted.
         assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
     }
 
     @Test
-    fun onTouchEvent_bottomSwipeWhenHubOpen_returnsFalse() {
+    fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() {
         // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
-        initAndAttachContainerView()
+        goToScene(CommunalSceneKey.Communal)
 
         // Touch event in the bottom swipe reqgion is not intercepted.
         assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
@@ -199,9 +225,7 @@
     @Test
     fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
         // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
-        initAndAttachContainerView()
+        goToScene(CommunalSceneKey.Communal)
 
         // Bouncer is visible.
         bouncerShowingFlow.value = true
@@ -216,9 +240,7 @@
     @Test
     fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
         // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
-        initAndAttachContainerView()
+        goToScene(CommunalSceneKey.Communal)
 
         shadeShowingFlow.value = true
         testableLooper.processAllMessages()
@@ -230,10 +252,7 @@
     @Test
     fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
         // Communal is open.
-        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
-
-        initAndAttachContainerView()
-        testableLooper.processAllMessages()
+        goToScene(CommunalSceneKey.Communal)
 
         // Touch events are intercepted.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
@@ -251,15 +270,24 @@
         parentView = FrameLayout(context)
         parentView.addView(containerView)
 
-        // Make view clickable so that dispatchTouchEvent returns true.
-        containerView.isClickable = true
-
         underTest.initView(containerView)
+
         // Attach the view so that flows start collecting.
         ViewUtils.attachView(parentView)
-        // Give the view a size so that determining if a touch starts at the right edge works.
-        parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
-        containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
+
+        // Give the view a fixed size to simplify testing for edge swipes.
+        val lp =
+            parentView.layoutParams.apply {
+                width = CONTAINER_WIDTH
+                height = CONTAINER_HEIGHT
+            }
+        val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+        wm.updateViewLayout(parentView, lp)
+    }
+
+    private fun goToScene(scene: CommunalSceneKey) {
+        communalRepository.setDesiredScene(scene)
+        testableLooper.processAllMessages()
     }
 
     companion object {
@@ -269,13 +297,17 @@
         private const val TOP_SWIPE_REGION_WIDTH = 20
         private const val BOTTOM_SWIPE_REGION_WIDTH = 20
 
+        /**
+         * A touch down event right in the middle of the screen, to avoid being in any of the swipe
+         * regions.
+         */
         private val DOWN_EVENT =
             MotionEvent.obtain(
                 0L,
                 0L,
                 MotionEvent.ACTION_DOWN,
-                CONTAINER_WIDTH.toFloat(),
-                CONTAINER_HEIGHT.toFloat(),
+                CONTAINER_WIDTH.toFloat() / 2,
+                CONTAINER_HEIGHT.toFloat() / 2,
                 0
             )
         private val DOWN_IN_RIGHT_SWIPE_REGION_EVENT =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b3e386e..cc27cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -189,7 +189,6 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -411,7 +410,7 @@
 
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
-                new FakeDeviceProvisioningRepository(),
+                mKosmos.getDeviceProvisioningInteractor(),
                 new FakeDisableFlagsRepository(),
                 mDozeParameters,
                 mFakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 461db8e..8a22f4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,13 +63,8 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -87,7 +82,6 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
@@ -99,7 +93,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -203,56 +196,12 @@
                 () -> sceneInteractor);
         CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
-        FakeKeyguardTransitionRepository keyguardTransitionRepository =
-                new FakeKeyguardTransitionRepository();
-
         KeyguardTransitionInteractor keyguardTransitionInteractor =
-                new KeyguardTransitionInteractor(
-                        mTestScope.getBackgroundScope(),
-                        keyguardTransitionRepository,
-                        () -> keyguardInteractor,
-                        () -> mFromLockscreenTransitionInteractor,
-                        () -> mFromPrimaryBouncerTransitionInteractor);
+                mKosmos.getKeyguardTransitionInteractor();
 
-        mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                mTestScope.getBackgroundScope(),
-                mKosmos.getTestDispatcher(),
-                mKosmos.getTestDispatcher(),
-                keyguardInteractor,
-                featureFlags,
-                shadeRepository,
-                powerInteractor,
-                new GlanceableHubTransitions(
-                        mTestScope,
-                        mKosmos.getTestDispatcher(),
-                        keyguardTransitionInteractor,
-                        keyguardTransitionRepository,
-                        communalInteractor
-                ),
-                () ->
-                        new InWindowLauncherUnlockAnimationInteractor(
-                                new InWindowLauncherUnlockAnimationRepository(),
-                                mTestScope.getBackgroundScope(),
-                                keyguardTransitionInteractor,
-                                () -> new FakeKeyguardSurfaceBehindRepository(),
-                                mock(ActivityManagerWrapper.class)
-                        )
-                );
-
-        mFromPrimaryBouncerTransitionInteractor = new FromPrimaryBouncerTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                mTestScope.getBackgroundScope(),
-                mKosmos.getTestDispatcher(),
-                mKosmos.getTestDispatcher(),
-                keyguardInteractor,
-                communalInteractor,
-                featureFlags,
-                mKeyguardSecurityModel,
-                mSelectedUserInteractor,
-                powerInteractor);
+        mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
+        mFromPrimaryBouncerTransitionInteractor =
+                mKosmos.getFromPrimaryBouncerTransitionInteractor();
 
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
@@ -260,7 +209,7 @@
 
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
-                new FakeDeviceProvisioningRepository(),
+                mKosmos.getDeviceProvisioningInteractor(),
                 new FakeDisableFlagsRepository(),
                 mock(DozeParameters.class),
                 keyguardRepository,
@@ -452,11 +401,11 @@
     }
 
     @Test
-    public void setCommunalShowing_userTimeout() {
+    public void setCommunalVisible_userTimeout() {
         setKeyguardShowing();
         clearInvocations(mWindowManager);
 
-        mNotificationShadeWindowController.onCommunalShowingChanged(true);
+        mNotificationShadeWindowController.onCommunalVisibleChanged(true);
         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
         assertThat(mLayoutParameters.getValue().userActivityTimeout)
                 .isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 697b05a..c226121 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -28,8 +28,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
@@ -94,7 +92,6 @@
 
     lateinit var underTest: NotificationsQSContainerController
 
-    private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var navigationModeCallback: ModeChangedListener
     private lateinit var taskbarVisibilityCallback: OverviewProxyListener
     private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -106,7 +103,6 @@
         MockitoAnnotations.initMocks(this)
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
-        featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
         mContext.ensureTestableResources()
         whenever(view.context).thenReturn(mContext)
         whenever(view.resources).thenReturn(mContext.resources)
@@ -123,7 +119,6 @@
                 shadeInteractor,
                 fragmentService,
                 delayableExecutor,
-                featureFlags,
                 notificationStackScrollLayoutController,
                 ResourcesSplitShadeStateController(),
                 largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -536,7 +531,6 @@
                 shadeInteractor,
                 fragmentService,
                 delayableExecutor,
-                featureFlags,
                 notificationStackScrollLayoutController,
                 ResourcesSplitShadeStateController(),
                 largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index e66251a..c326350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -28,8 +28,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
@@ -91,7 +89,6 @@
 
     lateinit var underTest: NotificationsQSContainerController
 
-    private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var navigationModeCallback: ModeChangedListener
     private lateinit var taskbarVisibilityCallback: OverviewProxyListener
     private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -104,7 +101,6 @@
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
         mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
-        featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
         mContext.ensureTestableResources()
         whenever(view.context).thenReturn(mContext)
         whenever(view.resources).thenReturn(mContext.resources)
@@ -122,7 +118,6 @@
                 shadeInteractor,
                 fragmentService,
                 delayableExecutor,
-                featureFlags,
                 notificationStackScrollLayoutController,
                 ResourcesSplitShadeStateController(),
                 largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -513,7 +508,6 @@
                 shadeInteractor,
                 fragmentService,
                 delayableExecutor,
-                featureFlags,
                 notificationStackScrollLayoutController,
                 ResourcesSplitShadeStateController(),
                 largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 3e0a647..f582402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -35,14 +35,12 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -51,13 +49,8 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -78,7 +71,6 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
 import com.android.systemui.shade.transition.ShadeTransitionController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -103,7 +95,6 @@
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -205,9 +196,7 @@
         mStatusBarStateController = mKosmos.getStatusBarStateController();
         mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
 
-        FakeDeviceProvisioningRepository deviceProvisioningRepository =
-                new FakeDeviceProvisioningRepository();
-        deviceProvisioningRepository.setDeviceProvisioned(true);
+        mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
@@ -232,58 +221,13 @@
                 new ConfigurationInteractor(configurationRepository),
                 mShadeRepository,
                 () -> sceneInteractor);
-        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
-
-        FakeKeyguardTransitionRepository keyguardTransitionRepository =
-                new FakeKeyguardTransitionRepository();
 
         KeyguardTransitionInteractor keyguardTransitionInteractor =
-                new KeyguardTransitionInteractor(
-                        mTestScope.getBackgroundScope(),
-                        keyguardTransitionRepository,
-                        () -> keyguardInteractor,
-                        () -> mFromLockscreenTransitionInteractor,
-                        () -> mFromPrimaryBouncerTransitionInteractor);
+                mKosmos.getKeyguardTransitionInteractor();
 
-        mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                mTestScope.getBackgroundScope(),
-                mKosmos.getTestDispatcher(),
-                mKosmos.getTestDispatcher(),
-                keyguardInteractor,
-                featureFlags,
-                mShadeRepository,
-                powerInteractor,
-                new GlanceableHubTransitions(
-                        mTestScope,
-                        mKosmos.getTestDispatcher(),
-                        keyguardTransitionInteractor,
-                        keyguardTransitionRepository,
-                        communalInteractor
-                ),
-                () ->
-                        new InWindowLauncherUnlockAnimationInteractor(
-                                new InWindowLauncherUnlockAnimationRepository(),
-                                mTestScope.getBackgroundScope(),
-                                keyguardTransitionInteractor,
-                                FakeKeyguardSurfaceBehindRepository::new,
-                                mock(ActivityManagerWrapper.class)
-                        )
-                );
-
-        mFromPrimaryBouncerTransitionInteractor = new FromPrimaryBouncerTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                mTestScope.getBackgroundScope(),
-                mKosmos.getTestDispatcher(),
-                mKosmos.getTestDispatcher(),
-                keyguardInteractor,
-                communalInteractor,
-                featureFlags,
-                mock(KeyguardSecurityModel.class),
-                mSelectedUserInteractor,
-                powerInteractor);
+        mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
+        mFromPrimaryBouncerTransitionInteractor =
+                mKosmos.getFromPrimaryBouncerTransitionInteractor();
 
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
@@ -294,7 +238,7 @@
 
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
-                deviceProvisioningRepository,
+                mKosmos.getDeviceProvisioningInteractor(),
                 mDisableFlagsRepository,
                 mDozeParameters,
                 mKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 50349be..0dd988d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -82,4 +82,22 @@
             underTest.setShowNotificationsOnLockscreenEnabled(false)
             assertThat(showNotifs).isEqualTo(false)
         }
+
+    @Test
+    fun testGetIsNotificationHistoryEnabled() =
+        testScope.runTest {
+            val historyEnabled by collectLastValue(underTest.isNotificationHistoryEnabled)
+
+            secureSettingsRepository.setInt(
+                name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                value = 1,
+            )
+            assertThat(historyEnabled).isEqualTo(true)
+
+            secureSettingsRepository.setInt(
+                name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                value = 0,
+            )
+            assertThat(historyEnabled).isEqualTo(false)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8cb064d..fb105e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,20 +27,17 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -56,8 +53,8 @@
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.flow.emptyFlow
@@ -74,8 +71,8 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -136,63 +133,15 @@
                 shadeRepository,
                 { kosmos.sceneInteractor },
             )
-        val keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(
-                testScope.backgroundScope,
-                keyguardTransitionRepository,
-                { keyguardInteractor },
-                { fromLockscreenTransitionInteractor },
-                { fromPrimaryBouncerTransitionInteractor }
-            )
-        val communalInteractor = kosmos.communalInteractor
-        fromLockscreenTransitionInteractor =
-            FromLockscreenTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                testScope.backgroundScope,
-                testDispatcher,
-                testDispatcher,
-                keyguardInteractor,
-                featureFlags,
-                shadeRepository,
-                powerInteractor,
-                GlanceableHubTransitions(
-                    testScope,
-                    testDispatcher,
-                    keyguardTransitionInteractor,
-                    keyguardTransitionRepository,
-                    communalInteractor
-                ),
-                {
-                    InWindowLauncherUnlockAnimationInteractor(
-                        InWindowLauncherUnlockAnimationRepository(),
-                        testScope,
-                        keyguardTransitionInteractor,
-                        { FakeKeyguardSurfaceBehindRepository() },
-                        mock(),
-                    )
-                }
-            )
-        fromPrimaryBouncerTransitionInteractor =
-            FromPrimaryBouncerTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                testScope.backgroundScope,
-                testDispatcher,
-                testDispatcher,
-                keyguardInteractor,
-                communalInteractor,
-                featureFlags,
-                mock(),
-                mock(),
-                powerInteractor
-            )
+        val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+        fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
+        fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
 
         whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
         shadeInteractor =
             ShadeInteractorImpl(
                 testScope.backgroundScope,
-                FakeDeviceProvisioningRepository(),
+                kosmos.deviceProvisioningInteractor,
                 FakeDisableFlagsRepository(),
                 mock(),
                 keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 350ed2d..7d99d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -21,7 +21,7 @@
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 57dac3a..cac4a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -17,10 +17,13 @@
 package com.android.systemui.statusbar.notification.footer.ui.view;
 
 import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -95,6 +98,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void setHistoryShown() {
         mView.showHistory(true);
         assertTrue(mView.isHistoryShown());
@@ -103,6 +107,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void setHistoryNotShown() {
         mView.showHistory(false);
         assertFalse(mView.isHistoryShown());
@@ -128,6 +133,62 @@
 
     @Test
     @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
+        int resId = R.string.manage_notifications_history_text;
+        mView.setManageOrHistoryButtonText(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.manage_text))
+                .getText().toString()).contains("History");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setManageOrHistoryButtonText(resId);
+        mView.setManageOrHistoryButtonText(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.manage_notifications_history_text;
+        assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
+        int resId = R.string.manage_notifications_history_text;
+        mView.setManageOrHistoryButtonDescription(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.manage_text))
+                .getContentDescription().toString()).contains("History");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setManageOrHistoryButtonDescription(resId);
+        mView.setManageOrHistoryButtonDescription(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.accessibility_clear_all;
+        assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
         int resId = R.string.clear_all_notifications_text;
         mView.setClearAllButtonText(resId);
@@ -150,7 +211,7 @@
     public void testSetClearAllButtonText_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.string.clear_all_notifications_text;
-        assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+        assertLogsWtf(() -> mView.setClearAllButtonText(resId));
         verify(mSpyContext, never()).getString(anyInt());
     }
 
@@ -178,7 +239,7 @@
     public void testSetClearAllButtonDescription_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.string.accessibility_clear_all;
-        assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+        assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
         verify(mSpyContext, never()).getString(anyInt());
     }
 
@@ -206,7 +267,7 @@
     public void testSetMessageString_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.string.unlock_to_see_notif_text;
-        assertLogsWtf(()-> mView.setMessageString(resId));
+        assertLogsWtf(() -> mView.setMessageString(resId));
         verify(mSpyContext, never()).getString(anyInt());
     }
 
@@ -231,7 +292,7 @@
     public void testSetMessageIcon_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.drawable.ic_friction_lock_closed;
-        assertLogsWtf(()-> mView.setMessageIcon(resId));
+        assertLogsWtf(() -> mView.setMessageIcon(resId));
         verify(mSpyContext, never()).getDrawable(anyInt());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 8ab13f5..620d972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -14,109 +14,61 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
-import org.junit.Before
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
 class FooterViewModelTest : SysuiTestCase() {
-    private lateinit var footerViewModel: FooterViewModel
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                ActivatableNotificationViewModelModule::class,
-                FooterViewModelModule::class,
-                HeadlessSystemUserModeModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val configurationRepository: FakeConfigurationRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val shadeRepository: FakeShadeRepository
-        val powerRepository: FakePowerRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+    private val shadeRepository = kosmos.shadeRepository
+    private val powerRepository = kosmos.powerRepository
+    private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
 
-    private val dozeParameters: DozeParameters = mock()
-
-    private val testComponent: TestComponent =
-        DaggerFooterViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParameters,
-                    )
-            )
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        // The underTest in the component is Optional, because that matches the provider we
-        // currently have for the footer view model.
-        footerViewModel = testComponent.underTest.get()
-    }
+    val underTest = kosmos.footerViewModel
 
     @Test
     fun testMessageVisible_whenFilteredNotifications() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.message.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.message.isVisible)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
 
@@ -125,8 +77,8 @@
 
     @Test
     fun testMessageVisible_whenNoFilteredNotifications() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.message.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.message.isVisible)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
 
@@ -135,8 +87,8 @@
 
     @Test
     fun testClearAllButtonVisible_whenHasClearableNotifs() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
@@ -153,8 +105,8 @@
 
     @Test
     fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
@@ -171,12 +123,12 @@
 
     @Test
     fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
             runCurrent()
 
             // WHEN shade is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
             shadeRepository.setLegacyShadeExpansion(1f)
             // AND QS not expanded
             shadeRepository.setQsExpansion(0f)
@@ -205,12 +157,12 @@
 
     @Test
     fun testClearAllButtonAnimating_whenShadeNotExpanded() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
             runCurrent()
 
             // WHEN shade is collapsed
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
             shadeRepository.setLegacyShadeExpansion(0f)
             // AND QS not expanded
             shadeRepository.setQsExpansion(0f)
@@ -236,4 +188,30 @@
             // THEN button visibility should not animate
             assertThat(visible?.isAnimating).isFalse()
         }
+
+    @Test
+    fun testManageButton_whenHistoryDisabled() =
+        testScope.runTest {
+            val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+            runCurrent()
+
+            // WHEN notification history is disabled
+            fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+            // THEN label is "Manage"
+            assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_text)
+        }
+
+    @Test
+    fun testHistoryButton_whenHistoryEnabled() =
+        testScope.runTest {
+            val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+            runCurrent()
+
+            // WHEN notification history is disabled
+            fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+            // THEN label is "History"
+            assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index da68d9c..5410864 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
+import android.app.Notification.CATEGORY_EVENT
+import android.app.Notification.CATEGORY_REMINDER
+import android.app.NotificationManager
 import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -47,6 +50,7 @@
             systemClock,
             uiEventLogger,
             userTracker,
+            avalancheProvider
         )
     }
 
@@ -70,6 +74,114 @@
         }
     }
 
+    // Avalanche tests are in VisualInterruptionDecisionProviderImplTest
+    // instead of VisualInterruptionDecisionProviderTestBase
+    // because avalanche code is based on the suppression refactor.
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_HIGH
+                isConversation = true
+                isImportantConversation = false
+                whenMs = whenAgo(5)
+            })
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEvent() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldNotHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_DEFAULT
+                isConversation = true
+                isImportantConversation = false
+                whenMs = whenAgo(15)
+            })
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowHighPriorityConversation() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_HIGH
+                isImportantConversation = true
+            })
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowCall() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_HIGH
+                isCall = true
+            })
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowCategoryReminder() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_HIGH
+                category = CATEGORY_REMINDER
+            })
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowCategoryEvent() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_HIGH
+                category = CATEGORY_EVENT
+            })
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowFsi() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            assertFsiNotSuppressed()
+        }
+    }
+
+    @Test
+    fun testAvalancheFilter_duringAvalanche_allowColorized() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+            ensurePeekState()
+            assertShouldHeadsUp(buildEntry {
+                importance = NotificationManager.IMPORTANCE_HIGH
+                isColorized = true
+            })
+        }
+    }
+
     @Test
     fun testPeekCondition_suppressesOnlyPeek() {
         withCondition(TestCondition(types = setOf(PEEK)) { true }) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 2ac0cb7..f89b9cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -19,7 +19,10 @@
 import android.app.ActivityManager
 import android.app.Notification
 import android.app.Notification.BubbleMetadata
+import android.app.Notification.EXTRA_COLORIZED
+import android.app.Notification.EXTRA_TEMPLATE
 import android.app.Notification.FLAG_BUBBLE
+import android.app.Notification.FLAG_CAN_COLORIZE
 import android.app.Notification.FLAG_FOREGROUND_SERVICE
 import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED
 import android.app.Notification.FLAG_USER_INITIATED_JOB
@@ -50,6 +53,8 @@
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.log.core.LogLevel
@@ -122,6 +127,7 @@
     protected val systemClock = FakeSystemClock()
     protected val uiEventLogger = UiEventLoggerFake()
     protected val userTracker = FakeUserTracker()
+    protected val avalancheProvider: AvalancheProvider = mock()
 
     protected abstract val provider: VisualInterruptionDecisionProvider
 
@@ -1097,6 +1103,8 @@
         var whenMs: Long? = null
         var isGrouped = false
         var isGroupSummary = false
+        var isCall = false
+        var category: String? = null
         var groupAlertBehavior: Int? = null
         var hasBubbleMetadata = false
         var hasFsi = false
@@ -1106,10 +1114,12 @@
         var isUserInitiatedJob = false
         var isBubble = false
         var isStickyAndNotDemoted = false
+        var isColorized = false
 
         // Set on NotificationEntryBuilder:
         var importance = IMPORTANCE_DEFAULT
         var canBubble: Boolean? = null
+        var isImportantConversation = false
 
         // Set on NotificationEntry:
         var hasJustLaunchedFsi = false
@@ -1118,6 +1128,7 @@
         var packageSuspended = false
         var visibilityOverride: Int? = null
         var suppressedVisualEffects: Int? = null
+        var isConversation = false
 
         private fun buildBubbleMetadata(): BubbleMetadata {
             val builder =
@@ -1158,6 +1169,13 @@
                         nb.setGroupSummary(true)
                     }
 
+                    if (isCall) {
+                        nb.extras.putString(EXTRA_TEMPLATE, Notification.CallStyle::class.java.name)
+                    }
+
+                    if (category != null) {
+                        nb.setCategory(category)
+                    }
                     groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) }
 
                     if (hasBubbleMetadata) {
@@ -1185,6 +1203,10 @@
                     if (isStickyAndNotDemoted) {
                         n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED
                     }
+                    if (isColorized) {
+                        n.extras.putBoolean(EXTRA_COLORIZED, true)
+                        n.flags = n.flags or FLAG_CAN_COLORIZE
+                    }
                 }
                 .let { NotificationEntryBuilder().setNotification(it) }
                 .also { neb ->
@@ -1193,9 +1215,10 @@
                     neb.setTag(TEST_TAG)
 
                     neb.setImportance(importance)
-                    neb.setChannel(
-                        NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
-                    )
+                    val channel =
+                            NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+                    channel.isImportantConversation = isImportantConversation
+                    neb.setChannel(channel)
 
                     canBubble?.let { neb.setCanBubble(it) }
                 }
@@ -1216,6 +1239,7 @@
                             }
                             visibilityOverride?.let { mrb.setVisibilityOverride(it) }
                             suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) }
+                            mrb.setIsConversation(isConversation)
                         }
                         .build()
                 }
@@ -1287,7 +1311,7 @@
         }
     }
 
-    private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
+    protected fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
 }
 
 private const val TEST_CONTENT_TITLE = "Test Content Title"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 0356c2c..620ad9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -19,6 +19,7 @@
 import android.os.Handler
 import android.os.PowerManager
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
@@ -49,7 +50,8 @@
         statusBarStateController: StatusBarStateController,
         systemClock: SystemClock,
         uiEventLogger: UiEventLogger,
-        userTracker: UserTracker
+        userTracker: UserTracker,
+        avalancheProvider: AvalancheProvider
     ): VisualInterruptionDecisionProvider {
         return if (VisualInterruptionRefactor.isEnabled) {
             VisualInterruptionDecisionProviderImpl(
@@ -67,7 +69,8 @@
                 statusBarStateController,
                 systemClock,
                 uiEventLogger,
-                userTracker
+                userTracker,
+                avalancheProvider
             )
         } else {
             NotificationInterruptStateProviderWrapper(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index dbe63f2..7589a49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 4188c5d..88e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -23,36 +23,27 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
-import com.android.systemui.unfold.UnfoldTransitionModule
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,46 +53,18 @@
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
 class NotificationListViewModelTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                ActivatableNotificationViewModelModule::class,
-                FooterViewModelModule::class,
-                HeadlessSystemUserModeModule::class,
-                UnfoldTransitionModule.Bindings::class,
-                NotificationStatsLoggerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<NotificationListViewModel> {
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val shadeRepository: FakeShadeRepository
-        val zenModeRepository: FakeZenModeRepository
-        val configurationController: FakeConfigurationController
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val fakeShadeRepository = kosmos.fakeShadeRepository
+    private val zenModeRepository = kosmos.zenModeRepository
+    private val fakeConfigurationController = kosmos.fakeConfigurationController
 
-    private val testComponent: TestComponent =
-        DaggerNotificationListViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
-                mocks = TestMocksModule()
-            )
+    val underTest = kosmos.notificationListViewModel
 
     @Before
     fun setUp() {
@@ -110,11 +73,11 @@
 
     @Test
     fun testIsImportantForAccessibility_falseWhenNoNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN on lockscreen
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
                 to = KeyguardState.LOCKSCREEN,
                 testScope,
@@ -129,11 +92,11 @@
 
     @Test
     fun testIsImportantForAccessibility_trueWhenNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN on lockscreen
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
                 to = KeyguardState.LOCKSCREEN,
                 testScope,
@@ -148,11 +111,11 @@
 
     @Test
     fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
-        testComponent.runTest {
+        testScope.runTest {
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN not on lockscreen
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
                 testScope,
@@ -167,7 +130,7 @@
 
     @Test
     fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
@@ -180,7 +143,7 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has notifs
@@ -193,13 +156,13 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            shadeRepository.legacyQsFullscreen.value = true
+            fakeShadeRepository.legacyQsFullscreen.value = true
             runCurrent()
 
             // THEN should not show
@@ -208,16 +171,16 @@
 
     @Test
     fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            shadeRepository.setQsExpansion(1f)
+            fakeShadeRepository.setQsExpansion(1f)
             // AND split shade is enabled
             overrideResource(R.bool.config_use_split_notification_shade, true)
-            configurationController.notifyConfigurationChanged()
+            fakeConfigurationController.notifyConfigurationChanged()
             runCurrent()
 
             // THEN should show
@@ -226,13 +189,13 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND transitioning to AOD
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     transitionState = TransitionState.STARTED,
                     from = KeyguardState.LOCKSCREEN,
@@ -248,13 +211,13 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND is on bouncer
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.PRIMARY_BOUNCER,
                 testScope,
@@ -267,7 +230,7 @@
 
     @Test
     fun testAreNotificationsHiddenInShade_true() =
-        testComponent.runTest {
+        testScope.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -279,7 +242,7 @@
 
     @Test
     fun testAreNotificationsHiddenInShade_false() =
-        testComponent.runTest {
+        testScope.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -291,7 +254,7 @@
 
     @Test
     fun testHasFilteredOutSeenNotifications_true() =
-        testComponent.runTest {
+        testScope.runTest {
             val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -302,7 +265,7 @@
 
     @Test
     fun testHasFilteredOutSeenNotifications_false() =
-        testComponent.runTest {
+        testScope.runTest {
             val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 32c727c..ff882b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -69,7 +69,10 @@
 
     val kosmos =
         testKosmos().apply {
-            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+            fakeFeatureFlagsClassic.apply {
+                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+            }
         }
 
     init {
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 849a13b..b048949 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
@@ -153,6 +153,7 @@
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
+import com.android.systemui.statusbar.notification.interruption.AvalancheProvider;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -313,6 +314,7 @@
     @Mock private CameraLauncher mCameraLauncher;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private UserTracker mUserTracker;
+    @Mock private AvalancheProvider mAvalancheProvider;
     @Mock private FingerprintManager mFingerprintManager;
     @Mock IPowerManager mPowerManagerService;
     @Mock ActivityStarter mActivityStarter;
@@ -367,7 +369,8 @@
                         mStatusBarStateController,
                         mFakeSystemClock,
                         mock(UiEventLogger.class),
-                        mUserTracker);
+                        mUserTracker,
+                        mAvalancheProvider);
         mVisualInterruptionDecisionProvider.start();
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 3bde6e3..99c2dc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -315,6 +315,8 @@
 
     @After
     public void tearDown() {
+        // Detaching view stops flow collection and prevents memory leak.
+        ViewUtils.detachView(mScrimBehind);
         finishAnimationsImmediately();
         Arrays.stream(ScrimState.values()).forEach((scrim) -> {
             scrim.setAodFrontScrimAlpha(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index cb45315..4015361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -79,6 +79,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -222,7 +223,8 @@
                         StandardTestDispatcher(null, null),
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
                         () -> mock(KeyguardDismissActionInteractor.class),
-                        mSelectedUserInteractor) {
+                        mSelectedUserInteractor,
+                        () -> mock(KeyguardSurfaceBehindInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -730,7 +732,8 @@
                         StandardTestDispatcher(null, null),
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
                         () -> mock(KeyguardDismissActionInteractor.class),
-                        mSelectedUserInteractor) {
+                        mSelectedUserInteractor,
+                        () -> mock(KeyguardSurfaceBehindInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
new file mode 100644
index 0000000..98be163
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.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.statusbar.policy
+
+import android.app.IActivityManager
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.server.notification.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@DisableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase() {
+    @Mock private lateinit var handler: Handler
+    @Mock private lateinit var activityManager: IActivityManager
+    @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+    private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        controller =
+            SensitiveNotificationProtectionControllerImpl(
+                mContext,
+                mediaProjectionManager,
+                activityManager,
+                handler,
+                FakeExecutor(FakeSystemClock())
+            )
+    }
+
+    @Test
+    fun init_noRegisterMediaProjectionManagerCallback() {
+        verifyZeroInteractions(mediaProjectionManager)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index cd5d5ed..a1aff48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -16,86 +16,94 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.app.ActivityOptions
+import android.app.IActivityManager
 import android.app.Notification
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
-import android.os.Handler
+import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
-import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
 class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
-    @Mock private lateinit var handler: Handler
-
+    @Mock private lateinit var activityManager: IActivityManager
     @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
-
     @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
-
     @Mock private lateinit var listener1: Runnable
     @Mock private lateinit var listener2: Runnable
     @Mock private lateinit var listener3: Runnable
 
-    @Captor
-    private lateinit var mediaProjectionCallbackCaptor:
-        ArgumentCaptor<MediaProjectionManager.Callback>
-
+    private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
     private lateinit var controller: SensitiveNotificationProtectionControllerImpl
 
     @Before
     fun setUp() {
+        allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners
         MockitoAnnotations.initMocks(this)
-        mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
 
-        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+        setShareFullScreen()
+        whenever(activityManager.bugreportWhitelistedPackages)
+            .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
+
+        val executor = FakeExecutor(FakeSystemClock())
+
+        controller =
+            SensitiveNotificationProtectionControllerImpl(
+                mContext,
+                mediaProjectionManager,
+                activityManager,
+                mockExecutorHandler(executor),
+                executor
+            )
+
+        // Process exemption processing
+        executor.runAllReady()
 
         // Obtain useful MediaProjectionCallback
-        verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+        mediaProjectionCallback = withArgCaptor {
+            verify(mediaProjectionManager).addCallback(capture(), any())
+        }
     }
 
     @Test
-    fun init_flagEnabled_registerMediaProjectionManagerCallback() {
-        assertNotNull(mediaProjectionCallbackCaptor.value)
-    }
-
-    @Test
-    fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
-        reset(mediaProjectionManager)
-
-        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
-
-        verifyZeroInteractions(mediaProjectionManager)
+    fun init_registerMediaProjectionManagerCallback() {
+        assertNotNull(mediaProjectionCallback)
     }
 
     @Test
     fun registerSensitiveStateListener_singleListener() {
         controller.registerSensitiveStateListener(listener1)
 
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verify(listener1, times(2)).run()
     }
@@ -105,8 +113,8 @@
         controller.registerSensitiveStateListener(listener1)
         controller.registerSensitiveStateListener(listener2)
 
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verify(listener1, times(2)).run()
         verify(listener2, times(2)).run()
@@ -114,12 +122,12 @@
 
     @Test
     fun registerSensitiveStateListener_afterProjectionActive() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         controller.registerSensitiveStateListener(listener1)
         verifyZeroInteractions(listener1)
 
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verify(listener1).run()
     }
@@ -128,15 +136,15 @@
     fun unregisterSensitiveStateListener_singleListener() {
         controller.registerSensitiveStateListener(listener1)
 
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verify(listener1, times(2)).run()
 
         controller.unregisterSensitiveStateListener(listener1)
 
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verifyNoMoreInteractions(listener1)
     }
@@ -147,8 +155,8 @@
         controller.registerSensitiveStateListener(listener2)
         controller.registerSensitiveStateListener(listener3)
 
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verify(listener1, times(2)).run()
         verify(listener2, times(2)).run()
@@ -157,8 +165,8 @@
         controller.unregisterSensitiveStateListener(listener1)
         controller.unregisterSensitiveStateListener(listener2)
 
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         verifyNoMoreInteractions(listener1)
         verifyNoMoreInteractions(listener2)
@@ -172,29 +180,55 @@
 
     @Test
     fun isSensitiveStateActive_projectionActive_true() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         assertTrue(controller.isSensitiveStateActive)
     }
 
     @Test
     fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
 
         assertFalse(controller.isSensitiveStateActive)
     }
 
     @Test
     fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStop(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         assertTrue(controller.isSensitiveStateActive)
     }
 
     @Test
+    fun isSensitiveStateActive_projectionActive_singleActivity_false() {
+        setShareSingleApp()
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
+        // SystemUi context packge name is exempt, but in test scenarios its
+        // com.android.systemui.tests so use that instead of hardcoding
+        whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
+        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
     fun shouldProtectNotification_projectionInactive_false() {
         val notificationEntry = mock(NotificationEntry::class.java)
 
@@ -202,30 +236,97 @@
     }
 
     @Test
-    fun shouldProtectNotification_projectionActive_fgsNotification_false() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+    fun shouldProtectNotification_projectionActive_singleActivity_false() {
+        setShareSingleApp()
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
-        val notificationEntry = mock(NotificationEntry::class.java)
-        val sbn = mock(StatusBarNotification::class.java)
-        val notification = mock(Notification::class.java)
-        `when`(notificationEntry.sbn).thenReturn(sbn)
-        `when`(sbn.notification).thenReturn(notification)
-        `when`(notification.isFgsOrUij).thenReturn(true)
+        val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
 
         assertFalse(controller.shouldProtectNotification(notificationEntry))
     }
 
     @Test
-    fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+    fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
+        val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
+
+        assertTrue(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+        assertTrue(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
+        // SystemUi context packge name is exempt, but in test scenarios its
+        // com.android.systemui.tests so use that instead of hardcoding
+        whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
+        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    private fun setShareFullScreen() {
+        whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+        whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
+    }
+
+    private fun setShareSingleApp() {
+        whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+        whenever(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+    }
+
+    private fun setupNotificationEntry(
+        packageName: String,
+        isFgs: Boolean = false
+    ): NotificationEntry {
         val notificationEntry = mock(NotificationEntry::class.java)
         val sbn = mock(StatusBarNotification::class.java)
         val notification = mock(Notification::class.java)
-        `when`(notificationEntry.sbn).thenReturn(sbn)
-        `when`(sbn.notification).thenReturn(notification)
-        `when`(notification.isFgsOrUij).thenReturn(false)
+        whenever(notificationEntry.sbn).thenReturn(sbn)
+        whenever(sbn.packageName).thenReturn(packageName)
+        whenever(sbn.notification).thenReturn(notification)
+        whenever(notification.isFgsOrUij).thenReturn(isFgs)
 
-        assertTrue(controller.shouldProtectNotification(notificationEntry))
+        return notificationEntry
+    }
+
+    private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
+        return setupNotificationEntry(packageName, /* isFgs= */ true)
+    }
+
+    companion object {
+        private const val TEST_PROJECTION_PACKAGE_NAME =
+            "com.android.systemui.statusbar.policy.projectionpackage"
+        private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
+        private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
index ba34ce6..bda339f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.app.tracing
+package com.android.systemui.tracing
 
 import android.os.Handler
 import android.os.Looper
@@ -20,11 +20,13 @@
 import android.testing.AndroidTestingRunner
 import android.util.Log
 import androidx.test.filters.SmallTest
+import com.android.app.tracing.TraceUtils.traceRunnable
+import com.android.app.tracing.namedRunnable
+import com.android.app.tracing.traceSection
 import com.android.systemui.SysuiTestCase
 import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -68,7 +70,6 @@
     }
 
     @Test
-    @Ignore("b/267482189 - Enable once androidx.tracing >= 1.2.0-beta04")
     fun testLongTraceSection_doesNotThrow_whenUsingAndroidX() {
         androidx.tracing.Trace.beginSection(SECTION_NAME_THATS_TOO_LONG)
     }
@@ -84,17 +85,13 @@
     fun testLongTraceSection_doesNotThrow_whenUsedAsTraceNameSupplier() {
         Handler(Looper.getMainLooper())
             .runWithScissors(
-                TraceUtils.namedRunnable(SECTION_NAME_THATS_TOO_LONG) {
-                    Log.v(TAG, "TraceUtils.namedRunnable() block.")
-                },
+                namedRunnable(SECTION_NAME_THATS_TOO_LONG) { Log.v(TAG, "namedRunnable() block.") },
                 TEST_FAIL_TIMEOUT
             )
     }
 
     @Test
     fun testLongTraceSection_doesNotThrow_whenUsingTraceRunnable() {
-        TraceUtils.traceRunnable(SECTION_NAME_THATS_TOO_LONG) {
-            Log.v(TAG, "TraceUtils.traceRunnable() block.")
-        }
+        traceRunnable(SECTION_NAME_THATS_TOO_LONG) { Log.v(TAG, "traceRunnable() block.") }.run()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt
new file mode 100644
index 0000000..27cd4b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/TestUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import junit.framework.Assert
+
+/**
+ * Assert that a list's values match the corresponding predicates.
+ *
+ * Useful to test animations or more complex objects where you only care about some of an object's
+ * properties.
+ */
+fun <T> List<T>.assertValuesMatch(vararg matchers: (T) -> Boolean) {
+    if (size != matchers.size) {
+        Assert.fail("Expected size ${matchers.size}, but was size $size:\n$this")
+    }
+
+    for (i in indices) {
+        if (!matchers[i].invoke(this[i])) {
+            Assert.fail("Assertion failed. Element #$i did not match:\n${this[i]}")
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
new file mode 100644
index 0000000..913759f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.content.pm.UserInfo
+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.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val secureSettings = FakeSettings()
+    private val userRepository = Kosmos().fakeUserRepository
+    private lateinit var repository: UserAwareSecureSettingsRepository
+
+    @Before
+    fun setup() {
+        repository = UserAwareSecureSettingsRepositoryImpl(
+            secureSettings,
+            userRepository,
+            dispatcher,
+        )
+        userRepository.setUserInfos(USER_INFOS)
+        setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+        setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+    }
+
+    @Test
+    fun settingEnabledEmitsValueForCurrentUser() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+
+            assertThat(enabled).isTrue()
+        }
+    }
+
+    @Test
+    fun settingEnabledEmitsNewValueWhenSettingChanges() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+            runCurrent()
+
+            setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+            assertThat(enabled).containsExactly(true, false).inOrder()
+        }
+    }
+
+    @Test
+    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+            assertThat(enabled).isFalse()
+        }
+    }
+
+    private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
+        secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+    }
+
+    private companion object {
+        const val SETTING_NAME = "SETTING_NAME"
+        val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+        val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+        val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 925ac2a..d45a9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -91,7 +91,6 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -99,7 +98,6 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -107,13 +105,8 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
-import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -137,7 +130,6 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationEntryHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -151,6 +143,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.interruption.AvalancheProvider;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger;
@@ -432,57 +425,12 @@
                 shadeRepository,
                 () -> sceneInteractor);
 
-        FakeKeyguardTransitionRepository keyguardTransitionRepository =
-                new FakeKeyguardTransitionRepository();
-
         KeyguardTransitionInteractor keyguardTransitionInteractor =
-                new KeyguardTransitionInteractor(
-                        mTestScope.getBackgroundScope(),
-                        keyguardTransitionRepository,
-                        () -> keyguardInteractor,
-                        () -> mFromLockscreenTransitionInteractor,
-                        () -> mFromPrimaryBouncerTransitionInteractor);
-        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
+                mKosmos.getKeyguardTransitionInteractor();
 
-        mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                mTestScope.getBackgroundScope(),
-                mKosmos.getTestDispatcher(),
-                mKosmos.getTestDispatcher(),
-                keyguardInteractor,
-                featureFlags,
-                shadeRepository,
-                powerInteractor,
-                new GlanceableHubTransitions(
-                        mTestScope,
-                        mKosmos.getTestDispatcher(),
-                        keyguardTransitionInteractor,
-                        keyguardTransitionRepository,
-                        communalInteractor
-                ),
-                () ->
-                        new InWindowLauncherUnlockAnimationInteractor(
-                                new InWindowLauncherUnlockAnimationRepository(),
-                                mTestScope.getBackgroundScope(),
-                                keyguardTransitionInteractor,
-                                FakeKeyguardSurfaceBehindRepository::new,
-                                mock(ActivityManagerWrapper.class)
-                        )
-                );
-
-        mFromPrimaryBouncerTransitionInteractor = new FromPrimaryBouncerTransitionInteractor(
-                keyguardTransitionRepository,
-                keyguardTransitionInteractor,
-                mTestScope.getBackgroundScope(),
-                mKosmos.getTestDispatcher(),
-                mKosmos.getTestDispatcher(),
-                keyguardInteractor,
-                communalInteractor,
-                featureFlags,
-                mock(KeyguardSecurityModel.class),
-                mSelectedUserInteractor,
-                powerInteractor);
+        mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
+        mFromPrimaryBouncerTransitionInteractor =
+                mKosmos.getFromPrimaryBouncerTransitionInteractor();
 
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
@@ -494,7 +442,7 @@
         mShadeInteractor =
                 new ShadeInteractorImpl(
                         mTestScope.getBackgroundScope(),
-                        deviceProvisioningRepository,
+                        mKosmos.getDeviceProvisioningInteractor(),
                         new FakeDisableFlagsRepository(),
                         mDozeParameters,
                         keyguardRepository,
@@ -587,7 +535,9 @@
                         mock(StatusBarStateController.class),
                         mock(SystemClock.class),
                         mock(UiEventLogger.class),
-                        mock(UserTracker.class));
+                        mock(UserTracker.class),
+                        mock(AvalancheProvider.class)
+                        );
         interruptionDecisionProvider.start();
 
         mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
@@ -616,7 +566,7 @@
                 mPositioner,
                 mock(DisplayController.class),
                 mOneHandedOptional,
-                Optional.of(mock(DragAndDropController.class)),
+                mock(DragAndDropController.class),
                 syncExecutor,
                 mock(Handler.class),
                 mTaskViewTransitions,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 9ad234e1..4a5ebd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -71,7 +71,7 @@
             BubblePositioner positioner,
             DisplayController displayController,
             Optional<OneHandedController> oneHandedOptional,
-            Optional<DragAndDropController> dragAndDropController,
+            DragAndDropController dragAndDropController,
             ShellExecutor shellMainExecutor,
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
diff --git a/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
index fb51f0f..37e3a590 100644
--- a/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
@@ -16,8 +16,10 @@
 
 package android.service.dream
 
+import android.app.DreamManager
 import android.service.dreams.IDreamManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.dreamManager by Kosmos.Fixture { mock<IDreamManager>() }
+var Kosmos.dreamManagerInterface by Kosmos.Fixture { mock<IDreamManager>() }
+val Kosmos.dreamManager by Kosmos.Fixture { mock<DreamManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
index 9059da2..20b9e84 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt
@@ -20,4 +20,4 @@
 import com.android.systemui.kosmos.Kosmos
 
 var Kosmos.uiEventLogger: UiEventLogger by Kosmos.Fixture { uiEventLoggerFake }
-val Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
+var Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
similarity index 63%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
index a368de2..901bdcc 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.keyguard.logging
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.scrimLogger by Kosmos.Fixture { mock<ScrimLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
index 961022f..a4f28f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
@@ -20,4 +20,4 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
+var Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
index 1c8bd3b..e9b7a69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
@@ -18,9 +18,12 @@
 package com.android.systemui.biometrics.data.repository
 
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 
 class FakeBiometricStatusRepository : BiometricStatusRepository {
     private val _fingerprintAuthenticationReason =
@@ -28,7 +31,16 @@
     override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> =
         _fingerprintAuthenticationReason.asStateFlow()
 
+    private val _fingerprintAcquiredStatus =
+        MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        _fingerprintAcquiredStatus.asStateFlow().filterNotNull()
+
     fun setFingerprintAuthenticationReason(reason: AuthenticationReason) {
         _fingerprintAuthenticationReason.value = reason
     }
+
+    fun setFingerprintAcquiredStatus(status: FingerprintAuthenticationStatus) {
+        _fingerprintAcquiredStatus.value = status
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
index 60f0448..3a61bf6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
@@ -17,11 +17,12 @@
 package com.android.systemui.common.data.repository
 
 import android.os.UserHandle
-import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.util.time.SystemClock
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.filter
 
-class FakePackageChangeRepository : PackageChangeRepository {
+class FakePackageChangeRepository(private val systemClock: SystemClock) : PackageChangeRepository {
 
     private var _packageChanged = MutableSharedFlow<PackageChangeModel>()
 
@@ -33,4 +34,60 @@
     suspend fun notifyChange(model: PackageChangeModel) {
         _packageChanged.emit(model)
     }
+
+    suspend fun notifyInstall(packageName: String, user: UserHandle) {
+        notifyChange(
+            PackageChangeModel.Installed(
+                packageName = packageName,
+                packageUid =
+                    UserHandle.getUid(
+                        /* userId = */ user.identifier,
+                        /* appId = */ packageName.hashCode(),
+                    ),
+                timeMillis = systemClock.currentTimeMillis(),
+            )
+        )
+    }
+
+    suspend fun notifyUpdateStarted(packageName: String, user: UserHandle) {
+        notifyChange(
+            PackageChangeModel.UpdateStarted(
+                packageName = packageName,
+                packageUid =
+                    UserHandle.getUid(
+                        /* userId = */ user.identifier,
+                        /* appId = */ packageName.hashCode(),
+                    ),
+                timeMillis = systemClock.currentTimeMillis(),
+            )
+        )
+    }
+
+    suspend fun notifyUpdateFinished(packageName: String, user: UserHandle) {
+        notifyChange(
+            PackageChangeModel.UpdateFinished(
+                packageName = packageName,
+                packageUid =
+                    UserHandle.getUid(
+                        /* userId = */ user.identifier,
+                        /* appId = */ packageName.hashCode(),
+                    ),
+                timeMillis = systemClock.currentTimeMillis(),
+            )
+        )
+    }
+
+    suspend fun notifyUninstall(packageName: String, user: UserHandle) {
+        notifyChange(
+            PackageChangeModel.Uninstalled(
+                packageName = packageName,
+                packageUid =
+                    UserHandle.getUid(
+                        /* userId = */ user.identifier,
+                        /* appId = */ packageName.hashCode(),
+                    ),
+                timeMillis = systemClock.currentTimeMillis(),
+            )
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
index adc05e0..57caa52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.common.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.fakeSystemClock
 
 var Kosmos.packageChangeRepository: PackageChangeRepository by
     Kosmos.Fixture { fakePackageChangeRepository }
-val Kosmos.fakePackageChangeRepository by Kosmos.Fixture { FakePackageChangeRepository() }
+val Kosmos.fakePackageChangeRepository by
+    Kosmos.Fixture { FakePackageChangeRepository(fakeSystemClock) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/PackageChangeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/PackageChangeInteractorKosmos.kt
new file mode 100644
index 0000000..e56d9d9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/PackageChangeInteractorKosmos.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.common.domain.interactor
+
+import com.android.systemui.common.data.repository.packageChangeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+var Kosmos.packageChangeInteractor by
+    Kosmos.Fixture { PackageChangeInteractor(packageChangeRepository, selectedUserInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ea3ccf..1884a32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -28,7 +28,7 @@
     fun mediaActive(timestamp: Long = 0L) {
         _mediaModel.value =
             CommunalMediaModel(
-                hasAnyMediaOrRecommendation = true,
+                hasActiveMediaOrRecommendation = true,
                 createdTimestampMillis = timestamp,
             )
     }
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 fab64e3..7301404 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
@@ -36,7 +36,7 @@
         }
     }
 
-    override fun deleteWidget(widgetId: Int) {
+    override fun deleteWidgetFromDb(widgetId: Int) {
         if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
             return
         }
@@ -44,6 +44,10 @@
         _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
     }
 
+    override fun deleteWidgetFromHost(widgetId: Int) {
+        deleteWidgetFromDb(widgetId)
+    }
+
     private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
         _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c818e9c..c47f020 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,12 +24,15 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.smartspace.data.repository.smartspaceRepository
 import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.mock
 
 val Kosmos.communalInteractor by Fixture {
     CommunalInteractor(
+        applicationScope = applicationCoroutineScope,
         communalRepository = communalRepository,
         widgetRepository = communalWidgetRepository,
         mediaRepository = communalMediaRepository,
@@ -39,6 +42,8 @@
         appWidgetHost = mock(),
         keyguardInteractor = keyguardInteractor,
         editWidgetsActivityStarter = editWidgetsActivityStarter,
+        logBuffer = logcatLogBuffer("CommunalInteractor"),
+        tableLogBuffer = mock(),
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index adaea7c..9776b43 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
 
 val Kosmos.communalTutorialInteractor by
     Kosmos.Fixture {
@@ -30,5 +31,6 @@
             keyguardInteractor = keyguardInteractor,
             communalRepository = communalRepository,
             communalInteractor = communalInteractor,
+            tableLogBuffer = mock(),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
new file mode 100644
index 0000000..109e113
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.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.controls.panels
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.authorizedPanelsRepository: AuthorizedPanelsRepository by
+    Kosmos.Fixture {
+        AuthorizedPanelsRepositoryImpl(applicationContext, fakeUserFileManager, fakeUserTracker)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
deleted file mode 100644
index a231212..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ /dev/null
@@ -1,74 +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.controls.panels
-
-import android.os.UserHandle
-import com.android.systemui.kosmos.Kosmos
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSelectedComponentRepository : SelectedComponentRepository {
-    private var shouldAddDefaultPanel: Boolean = true
-    private val _selectedComponentFlows =
-        mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
-    private var currentUserHandle: UserHandle = UserHandle.of(0)
-
-    override fun selectedComponentFlow(
-        userHandle: UserHandle
-    ): Flow<SelectedComponentRepository.SelectedComponent?> {
-        // Return an existing flow for the user or create a new one
-        return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
-            MutableStateFlow(null)
-        }
-    }
-
-    override fun getSelectedComponent(
-        userHandle: UserHandle
-    ): SelectedComponentRepository.SelectedComponent? {
-        return _selectedComponentFlows[getUserHandle(userHandle)]?.value
-    }
-
-    override fun setSelectedComponent(
-        selectedComponent: SelectedComponentRepository.SelectedComponent
-    ) {
-        val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
-        flow.value = selectedComponent
-    }
-
-    override fun removeSelectedComponent() {
-        _selectedComponentFlows[currentUserHandle]?.value = null
-    }
-    override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
-
-    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
-        shouldAddDefaultPanel = shouldAdd
-    }
-
-    fun setCurrentUserHandle(userHandle: UserHandle) {
-        currentUserHandle = userHandle
-    }
-    private fun getUserHandle(userHandle: UserHandle): UserHandle {
-        return if (userHandle == UserHandle.CURRENT) {
-            currentUserHandle
-        } else {
-            userHandle
-        }
-    }
-}
-
-val Kosmos.selectedComponentRepository by
-    Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
new file mode 100644
index 0000000..ee5b7e5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.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.controls.panels
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.selectedComponentRepository: SelectedComponentRepository by
+    Kosmos.Fixture {
+        SelectedComponentRepositoryImpl(fakeUserFileManager, fakeUserTracker, testDispatcher)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 7b36a29..365d97f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -35,6 +35,7 @@
         FakeFeatureFlagsClassic().apply {
             set(Flags.FULL_SCREEN_USER_SWITCHER, false)
             set(Flags.NSSL_DEBUG_LINES, false)
+            set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
         }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.kt
new file mode 100644
index 0000000..046946e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/LightRevealScrimRepositoryKosmos.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.keyguard.data
+
+import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lightRevealScrimRepository by Kosmos.Fixture { fakeLightRevealScrimRepository }
+
+var Kosmos.fakeLightRevealScrimRepository by Kosmos.Fixture { FakeLightRevealScrimRepository() }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
index a368de2..d0a0e1d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.keyguard.data.repository
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeCommandQueue by Kosmos.Fixture { FakeCommandQueue() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 85a233fd..534f773 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -20,6 +20,7 @@
 import com.android.keyguard.KeyguardClockSwitch.ClockSize
 import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
 import com.android.systemui.util.mockito.mock
@@ -29,6 +30,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import org.mockito.Mockito
 
 class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepository {
     private val _clockSize = MutableStateFlow(LARGE)
@@ -42,6 +44,9 @@
 
     private val _currentClock = MutableStateFlow(null)
     override val currentClock = _currentClock
+
+    private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
+    override val previewClock: StateFlow<ClockController> = _previewClock
     override val clockEventController: ClockEventController
         get() = mock()
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 008f79a..408157b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -20,4 +20,4 @@
 
 var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
     Kosmos.Fixture { fakeKeyguardTransitionRepository }
-val Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..da5cd67
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.fromAodTransitionInteractor by
+    Kosmos.Fixture {
+        FromAodTransitionInteractor(
+            transitionRepository = fakeKeyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = testScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..25fc67a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.fromGoneTransitionInteractor by
+    Kosmos.Fixture {
+        FromGoneTransitionInteractor(
+            transitionRepository = fakeKeyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            powerInteractor = powerInteractor,
+            communalInteractor = communalInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 3b38342..3b52676 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -23,9 +23,8 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
-import dagger.Lazy
 
-val Kosmos.fromLockscreenTransitionInteractor by
+var Kosmos.fromLockscreenTransitionInteractor by
     Kosmos.Fixture {
         FromLockscreenTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
@@ -38,7 +37,6 @@
             shadeRepository = shadeRepository,
             powerInteractor = powerInteractor,
             glanceableHubTransitions = glanceableHubTransitions,
-            inWindowLauncherUnlockAnimationInteractor =
-                Lazy { inWindowLauncherUnlockAnimationInteractor },
+            swipeToDismissInteractor = swipeToDismissInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 719686e..6b76449 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
-val Kosmos.fromPrimaryBouncerTransitionInteractor by
+var Kosmos.fromPrimaryBouncerTransitionInteractor by
     Kosmos.Fixture {
         FromPrimaryBouncerTransitionInteractor(
             transitionRepository = keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 638a6a3..c06f833 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -18,12 +18,12 @@
 
 import android.content.applicationContext
 import android.view.accessibility.accessibilityManagerWrapper
+import com.android.internal.logging.uiEventLogger
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.qs.uiEventLogger
 
 val Kosmos.keyguardLongPressInteractor by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
new file mode 100644
index 0000000..c9c17d9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.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.keyguard.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
+
+var Kosmos.keyguardSurfaceBehindInteractor by
+    Kosmos.Fixture {
+        KeyguardSurfaceBehindInteractor(
+            repository = keyguardSurfaceBehindRepository,
+            context = applicationContext,
+            transitionInteractor = keyguardTransitionInteractor,
+            inWindowLauncherUnlockAnimationInteractor = {
+                inWindowLauncherUnlockAnimationInteractor
+            },
+            swipeToDismissInteractor = swipeToDismissInteractor,
+            notificationLaunchInteractor = notificationLaunchAnimationInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
deleted file mode 100644
index 5cf656c..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ /dev/null
@@ -1,69 +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.keyguard.domain.interactor
-
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.util.mockito.mock
-import dagger.Lazy
-import kotlinx.coroutines.CoroutineScope
-
-/**
- * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
- * tests whenever we add a constructor param.
- */
-object KeyguardTransitionInteractorFactory {
-    @JvmOverloads
-    @JvmStatic
-    fun create(
-        scope: CoroutineScope,
-        repository: FakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
-        featureFlags: FakeFeatureFlags = FakeFeatureFlagsClassic(),
-        keyguardInteractor: KeyguardInteractor =
-            KeyguardInteractorFactory.create(featureFlags = featureFlags).keyguardInteractor,
-        fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
-            mock<FromLockscreenTransitionInteractor>()
-        },
-        fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
-            Lazy {
-                mock<FromPrimaryBouncerTransitionInteractor>()
-            },
-    ): WithDependencies {
-        return WithDependencies(
-            repository = repository,
-            keyguardInteractor = keyguardInteractor,
-            fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
-            fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
-            KeyguardTransitionInteractor(
-                scope = scope,
-                repository = repository,
-                keyguardInteractor = { keyguardInteractor },
-                fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
-                fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
-            )
-        )
-    }
-
-    data class WithDependencies(
-        val repository: FakeKeyguardTransitionRepository,
-        val keyguardInteractor: KeyguardInteractor,
-        val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
-        val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
-        val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index e4d115e..0c38fd9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -30,5 +30,6 @@
             fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
             fromPrimaryBouncerTransitionInteractor =
                 Lazy { fromPrimaryBouncerTransitionInteractor },
+            fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
new file mode 100644
index 0000000..58e0a3b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.keyguard.logging.scrimLogger
+import com.android.systemui.keyguard.data.lightRevealScrimRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.lightRevealScrimInteractor by
+    Kosmos.Fixture {
+        LightRevealScrimInteractor(
+            keyguardTransitionInteractor,
+            lightRevealScrimRepository,
+            applicationCoroutineScope,
+            scrimLogger,
+            powerInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorKosmos.kt
new file mode 100644
index 0000000..25aad04
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.data.repository.shadeRepository
+
+val Kosmos.swipeToDismissInteractor by
+    Kosmos.Fixture {
+        SwipeToDismissInteractor(
+            backgroundScope = applicationCoroutineScope,
+            shadeRepository = shadeRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
new file mode 100644
index 0000000..d84988d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
+
+val Kosmos.windowManagerLockscreenVisibilityInteractor by
+    Kosmos.Fixture {
+        WindowManagerLockscreenVisibilityInteractor(
+            keyguardInteractor = keyguardInteractor,
+            transitionInteractor = keyguardTransitionInteractor,
+            surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
+            fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+            fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+            notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d376f12..24bb9c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -38,6 +38,9 @@
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+        lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+        alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+        primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
         screenOffAnimationController = screenOffAnimationController,
         aodBurnInViewModel = aodBurnInViewModel,
         aodAlphaViewModel = aodAlphaViewModel,
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 083de10..10305f7 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
@@ -33,6 +33,8 @@
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.model.sceneContainerPlugin
 import com.android.systemui.plugins.statusbar.statusBarStateController
@@ -43,6 +45,8 @@
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.statusbar.phone.screenOffAnimationController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.util.time.systemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -80,6 +84,12 @@
     val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
     val communalInteractor by lazy { kosmos.communalInteractor }
     val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
+    val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
+    val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
+    val fromLockscreenTransitionInteractor by lazy { kosmos.fromLockscreenTransitionInteractor }
+    val fromPrimaryBouncerTransitionInteractor by lazy {
+        kosmos.fromPrimaryBouncerTransitionInteractor
+    }
 
     init {
         kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 73b7c50..be559ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.plugins.statusbar
 
+import com.android.internal.logging.uiEventLogger
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.uiEventLogger
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 6332c1a..23d657d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs
 
-import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.plugins.qs.QSFactory
@@ -24,9 +24,8 @@
 
 val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
     Kosmos.Fixture { InstanceIdSequenceFake(0) }
-val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
 val Kosmos.qsEventLogger: QsEventLoggerFake by
-    Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) }
+    Kosmos.Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) }
 
 var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
 var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
new file mode 100644
index 0000000..9410ce6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.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.tiles.impl.fontscaling
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsFontScalingTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideFontScalingTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
new file mode 100644
index 0000000..207c3f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.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.settings
+
+import android.content.SharedPreferences
+import com.android.systemui.util.FakeSharedPreferences
+import java.io.File
+
+class FakeUserFileManager : UserFileManager {
+    private val sharedPreferences = mutableMapOf<SharedPrefKey, FakeSharedPreferences>()
+
+    override fun getFile(fileName: String, userId: Int): File {
+        throw UnsupportedOperationException("getFile not implemented in fake")
+    }
+
+    override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+        val key = SharedPrefKey(fileName, mode, userId)
+        return sharedPreferences.getOrPut(key) { FakeSharedPreferences() }
+    }
+
+    private data class SharedPrefKey(val fileName: String, val mode: Int, val userId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
new file mode 100644
index 0000000..4d7a40a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.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.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserFileManager by Kosmos.Fixture { FakeUserFileManager() }
+var Kosmos.userFileManager: UserFileManager by Kosmos.Fixture { fakeUserFileManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index e13fa52..82e0b8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -44,6 +45,7 @@
 val Kosmos.shadeControllerSceneImpl by
     Kosmos.Fixture {
         ShadeControllerSceneImpl(
+            mainDispatcher = testDispatcher,
             scope = applicationCoroutineScope,
             shadeInteractor = shadeInteractor,
             sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index f7005ab..4aab822 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -22,6 +22,7 @@
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 
 /** Fake implementation of [ShadeRepository] */
 @SysUISingleton
@@ -32,6 +33,9 @@
     private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
     override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
 
+    private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null)
+    override val currentFling: StateFlow<FlingInfo?> = _currentFling
+
     private val _lockscreenShadeExpansion = MutableStateFlow(0f)
     override val lockscreenShadeExpansion = _lockscreenShadeExpansion
 
@@ -115,6 +119,10 @@
         _udfpsTransitionToFullShadeProgress.value = progress
     }
 
+    override fun setCurrentFling(info: FlingInfo?) {
+        _currentFling.value = info
+    }
+
     override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
         _lockscreenShadeExpansion.value = lockscreenShadeExpansion
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
new file mode 100644
index 0000000..5dc0333
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.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.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.shadeBackActionInteractor by
+    Kosmos.Fixture {
+        ShadeBackActionInteractorImpl(
+            shadeInteractor = shadeInteractor,
+            sceneInteractor = sceneInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index afd37b3..2bd76be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.user.domain.interactor.userSwitcherInteractor
 
 var Kosmos.baseShadeInteractor: BaseShadeInteractor by
@@ -63,7 +63,7 @@
     Kosmos.Fixture {
         ShadeInteractorImpl(
             scope = applicationCoroutineScope,
-            deviceProvisioningRepository = deviceProvisioningRepository,
+            deviceProvisioningInteractor = deviceProvisioningInteractor,
             disableFlagsRepository = disableFlagsRepository,
             dozeParams = dozeParameters,
             keyguardRepository = fakeKeyguardRepository,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
similarity index 60%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
index a368de2..5638cfc 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.statusbar.notification.data.repository
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.notificationLaunchAnimationRepository by
+    Kosmos.Fixture { NotificationLaunchAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..0d84bab
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.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.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.notificationLaunchAnimationRepository
+
+val Kosmos.notificationLaunchAnimationInteractor by
+    Kosmos.Fixture {
+        NotificationLaunchAnimationInteractor(
+            repository = notificationLaunchAnimationRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index ff22ca0..01cac4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 
 val Kosmos.footerViewModel by Fixture {
     FooterViewModel(
         activeNotificationsInteractor = activeNotificationsInteractor,
+        notificationSettingsInteractor = notificationSettingsInteractor,
         seenNotificationsInteractor = seenNotificationsInteractor,
         shadeInteractor = shadeInteractor,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 748d04d..489598c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.notificationActivityStarter
 import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
@@ -36,9 +37,10 @@
         configuration = configurationState,
         falsingManager = falsingManager,
         iconAreaController = notificationIconAreaController,
+        loggerOptional = Optional.of(notificationStatsLogger),
         metricsLogger = metricsLogger,
         hiderTracker = displaySwitchNotificationsHiderTracker,
         nicBinder = notificationIconContainerShelfViewBinder,
-        loggerOptional = Optional.of(notificationStatsLogger),
+        notificationActivityStarter = { notificationActivityStarter },
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index d7e948e..29faa58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.shared.flag.sceneContainerFlags
@@ -29,5 +30,6 @@
         shadeInteractor = shadeInteractor,
         flags = sceneContainerFlags,
         featureFlags = featureFlagsClassic,
+        keyguardInteractor = keyguardInteractor,
     )
 }
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 549a775..30d4105 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
@@ -19,13 +19,16 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 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.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,6 +44,9 @@
         shadeInteractor = shadeInteractor,
         communalInteractor = communalInteractor,
         occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+        lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+        alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+        primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
         lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
         dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
         lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
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 6ddc9df..8042b5c 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
@@ -19,7 +19,7 @@
 import android.app.keyguardManager
 import android.content.applicationContext
 import android.os.fakeExecutorHandler
-import android.service.dream.dreamManager
+import android.service.dream.dreamManagerInterface
 import com.android.internal.logging.metricsLogger
 import com.android.internal.widget.lockPatternUtils
 import com.android.systemui.activityIntentHelper
@@ -62,7 +62,7 @@
             notificationClickNotifier,
             statusBarKeyguardViewManager,
             keyguardManager,
-            dreamManager,
+            dreamManagerInterface,
             Optional.of(bubblesManager),
             { assistManager },
             notificationRemoteInputManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 33ed7e6..d4e9bfb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.policy
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
 
-val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+var Kosmos.configurationController: ConfigurationController by
+    Kosmos.Fixture { fakeConfigurationController }
 val Kosmos.fakeConfigurationController: FakeConfigurationController by
     Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index 3002299..fc6a800 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -24,7 +24,7 @@
 
 @SysUISingleton
 class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
-    private val _isDeviceProvisioned = MutableStateFlow(false)
+    private val _isDeviceProvisioned = MutableStateFlow(true)
     override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
     private val _isFactoryResetProtectionActive = MutableStateFlow(false)
     override val isFactoryResetProtectionActive: Flow<Boolean> = _isFactoryResetProtectionActive
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
new file mode 100644
index 0000000..84bd3e8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.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.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+
+val Kosmos.deviceProvisioningInteractor by Fixture {
+    DeviceProvisioningInteractor(
+        repository = deviceProvisioningRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
new file mode 100644
index 0000000..5054e29
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.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.util.settings
+
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
+
+    private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
+
+    override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
+        return settings.map { it.getOrDefault(name, defaultValue) }
+    }
+
+    fun setBoolSettingForActiveUser(name: String, value: Boolean) {
+        settings.value = settings.value.toMutableMap().apply { this[name] = value }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
similarity index 62%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
index a368de2..94b2bdf 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.util.settings
 
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.userAwareSecureSettingsRepository by
+    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f68baf5..592c6f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -30,11 +30,11 @@
     private final List<FlashlightListener> callbacks = new ArrayList<>();
 
     @VisibleForTesting
-    public boolean isAvailable;
+    public boolean isAvailable = true;
     @VisibleForTesting
-    public boolean isEnabled;
+    public boolean isEnabled = false;
     @VisibleForTesting
-    public boolean hasFlashlight;
+    public boolean hasFlashlight = true;
 
     public FakeFlashlightController(LeakCheck test) {
         super(test, "flashlight");
@@ -52,16 +52,26 @@
         callbacks.forEach(FlashlightListener::onFlashlightError);
     }
 
+    /**
+     * Used to decide if tile should be shown or gone
+     * @return available/unavailable
+     */
     @Override
     public boolean hasFlashlight() {
         return hasFlashlight;
     }
 
+    /**
+     * @param newState active/inactive
+     */
     @Override
     public void setFlashlight(boolean newState) {
         callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
     }
 
+    /**
+     * @return temporary availability
+     */
     @Override
     public boolean isAvailable() {
         return isAvailable;
@@ -76,6 +86,9 @@
     public void addCallback(FlashlightListener listener) {
         super.addCallback(listener);
         callbacks.add(listener);
+
+        listener.onFlashlightAvailabilityChanged(isAvailable());
+        listener.onFlashlightChanged(isEnabled());
     }
 
     @Override
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce..32b7020 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 155c523..c134a4c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -120,6 +120,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -142,11 +143,13 @@
     // Support for various latency improvements
     private static final String LATENCY_VERSION_PREFIX = "1.4";
     private static final String EFV_VERSION_PREFIX = "1.5";
+    private static final String GET_VERSION_PREFIX = "1.5";
     private static final String[] ADVANCED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
-            LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
+            LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX,
+            GET_VERSION_PREFIX};
     private static final String[] SUPPORTED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
             LATENCY_VERSION_PREFIX, RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1",
-            NON_INIT_VERSION_PREFIX};
+            NON_INIT_VERSION_PREFIX, GET_VERSION_PREFIX};
     private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
     private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
             (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
@@ -156,6 +159,8 @@
                     (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)));
     private static final boolean EFV_SUPPORTED = EXTENSIONS_PRESENT &&
             (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
+    private static final boolean GET_API_SUPPORTED = EXTENSIONS_PRESENT
+            && (EXTENSIONS_VERSION.startsWith(GET_VERSION_PREFIX));
     private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI();
     private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT &&
             (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX));
@@ -777,6 +782,12 @@
                         public boolean isPostviewAvailable() {
                             return false;
                         }
+
+                        @Override
+                        public List<Pair<CameraCharacteristics.Key, Object>>
+                                getAvailableCharacteristicsKeyValues() {
+                            return Collections.emptyList();
+                        }
                     };
                 }
             }
@@ -1186,6 +1197,35 @@
 
             return false;
         }
+
+        @Override
+        public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) {
+            if (GET_API_SUPPORTED) {
+                List<Pair<CameraCharacteristics.Key, Object>> entries =
+                        mAdvancedExtender.getAvailableCharacteristicsKeyValues();
+
+                if ((entries != null) && !entries.isEmpty()) {
+                    CameraMetadataNative ret = new CameraMetadataNative();
+                    long vendorId = mMetadataVendorIdMap.containsKey(cameraId)
+                            ? mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE;
+                    ret.setVendorId(vendorId);
+                    int[] characteristicsKeyTags = new int[entries.size()];
+                    int i = 0;
+                    for (Pair<CameraCharacteristics.Key, Object> entry : entries) {
+                        int tag = CameraMetadataNative.getTag(entry.first.getName(), vendorId);
+                        characteristicsKeyTags[i++] = tag;
+                        ret.set(entry.first, entry.second);
+                    }
+                    ret.set(CameraCharacteristics.REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+                            characteristicsKeyTags);
+
+                    return ret;
+                }
+            }
+
+            return null;
+        }
+
     }
 
     private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
deleted file mode 100644
index 6adb6144..0000000
--- a/ravenwood/README-ravenwood+mockito.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Ravenwood and Mockito
-
-Last update: 2023-11-13
-
-- As of 2023-11-13, `external/mockito` is based on version 2.x.
-- Mockito didn't support static mocking before 3.4.0.
-  See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
-
-- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
-  `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
-
-- Mockito now supports Android natively.
-  See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
-  - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
-
-- Potential plan:
-  - Ideal option:
-    - If we can update `external/mockito`, that'd be great, but it may not work because
-      Mockito has removed the deprecated APIs.
-  - Second option:
-    - Import the latest mockito as `external/mockito-new`, and require ravenwood
-      to use this one.
-    - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
-    - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
index 36d398c..aafaaff 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -21,7 +21,7 @@
 classes that have partial success.
 
 Typical usage:
-$ ENABLE_PROBE_IGNORED=1 atest MyTestsRavenwood
+$ RAVENWOOD_RUN_DISABLED_TESTS=1 atest MyTestsRavenwood
 $ cd /path/to/tests/root
 $ python bulk_enable.py /path/to/atest/output/host_log.txt
 """
@@ -34,6 +34,8 @@
 
 re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
 
+DRY_RUN = "-n" in sys.argv
+
 ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
 SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
 
@@ -46,7 +48,7 @@
 stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
 stats_method = collections.defaultdict()
 
-with open(sys.argv[1]) as f:
+with open(sys.argv[-1]) as f:
     for line in f.readlines():
         result = re_result.search(line)
         if result:
@@ -67,7 +69,7 @@
         clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
         for root, dirs, files in os.walk("."):
             for f in files:
-                if clazz_match.match(f):
+                if clazz_match.match(f) and not DRY_RUN:
                     path = os.path.join(root, f)
                     subprocess.run(["sed", "-i", "-E", SED_ARG, path])
 
diff --git a/ravenwood/coretest/Android.bp b/ravenwood/coretest/Android.bp
new file mode 100644
index 0000000..9b7f8f7
--- /dev/null
+++ b/ravenwood/coretest/Android.bp
@@ -0,0 +1,23 @@
+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"],
+}
+
+android_ravenwood_test {
+    name: "RavenwoodCoreTest",
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+    ],
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    sdk_version: "test_current",
+    auto_gen_config: true,
+}
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
new file mode 100644
index 0000000..e58c282
--- /dev/null
+++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.coretest;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for the test runner validator in RavenwoodRule.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodTestRunnerValidationTest {
+    // Note the following rules don't have a @Rule, because they need to be applied in a specific
+    // order. So we use a RuleChain instead.
+    private ExpectedException mThrown = ExpectedException.none();
+    private final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Rule
+    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
+
+    public RavenwoodTestRunnerValidationTest() {
+        Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+        // Because RavenwoodRule will throw this error before executing the test method,
+        // we can't do it in the test method itself.
+        // So instead, we initialize it here.
+        mThrown.expectMessage("Switch to androidx.test.ext.junit.runners.AndroidJUnit4");
+    }
+
+    @Test
+    public void testValidateTestRunner() {
+    }
+}
diff --git a/ravenwood/fix_test_runner.py b/ravenwood/fix_test_runner.py
new file mode 100755
index 0000000..99b7a1f
--- /dev/null
+++ b/ravenwood/fix_test_runner.py
@@ -0,0 +1,68 @@
+#!/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.
+
+"""
+Tool switch the deprecated jetpack test runner to the correct one.
+
+Typical usage:
+$ RAVENWOOD_OPTIONAL_VALIDATION=1 atest MyTestsRavenwood # Prepend RAVENWOOD_RUN_DISABLED_TESTS=1 as needed
+$ cd /path/to/tests/root
+$ python bulk_enable.py /path/to/atest/output/host_log.txt
+"""
+
+import collections
+import os
+import re
+import subprocess
+import sys
+
+re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+
+OLD_RUNNER = "androidx.test.runner.AndroidJUnit4"
+NEW_RUNNER = "androidx.test.ext.junit.runners.AndroidJUnit4"
+SED_ARG = r"s/%s/%s/g" % (OLD_RUNNER, NEW_RUNNER)
+
+target = collections.defaultdict()
+
+with open(sys.argv[1]) as f:
+    for line in f.readlines():
+        result = re_result.search(line)
+        if result:
+            clazz, method, state, msg = result.groups()
+            if NEW_RUNNER in msg:
+                target[clazz] = 1
+
+if len(target) == 0:
+    print("No tests need updating.")
+    sys.exit(0)
+
+num_fixed = 0
+for clazz in target.keys():
+    print("Fixing test runner", clazz)
+    clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
+    found = False
+    for root, dirs, files in os.walk("."):
+        for f in files:
+            if clazz_match.match(f):
+                found = True
+                num_fixed += 1
+                path = os.path.join(root, f)
+                subprocess.run(["sed", "-i", "-E", SED_ARG, path])
+    if not found:
+        print(f"  Warining: tests {clazz} not found")
+
+
+print("Tests fixed", num_fixed)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3670459..7b5932b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,7 +16,9 @@
 
 package android.platform.test.ravenwood;
 
+import android.app.ActivityManager;
 import android.app.Instrumentation;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -26,14 +28,19 @@
 
 import com.android.internal.os.RuntimeInit;
 
+import org.junit.Assert;
 import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
 
 import java.io.PrintStream;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 public class RavenwoodRuleImpl {
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -50,11 +57,34 @@
 
     private static ScheduledFuture<?> sPendingTimeout;
 
+    /**
+     * When enabled, attempt to detect uncaught exceptions from background threads.
+     */
+    private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION = false;
+
+    /**
+     * When set, an unhandled exception was discovered (typically on a background thread), and we
+     * capture it here to ensure it's reported as a test failure.
+     */
+    private static final AtomicReference<Throwable> sPendingUncaughtException =
+            new AtomicReference<>();
+
+    private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
+            (thread, throwable) -> {
+                // Remember the first exception we discover
+                sPendingUncaughtException.compareAndSet(null, throwable);
+            };
+
     public static boolean isOnRavenwood() {
         return true;
     }
 
     public static void init(RavenwoodRule rule) {
+        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+            maybeThrowPendingUncaughtException(false);
+            Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+        }
+
         RuntimeInit.redirectLogStreams();
 
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
@@ -64,6 +94,8 @@
                 rule.mSystemProperties.getKeyReadablePredicate(),
                 rule.mSystemProperties.getKeyWritablePredicate());
 
+        ActivityManager.init$ravenwood(rule.mCurrentUser);
+
         com.android.server.LocalServices.removeAllServicesForTest();
 
         if (rule.mProvideMainThread) {
@@ -78,6 +110,10 @@
             sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
                     TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
+
+        // Touch some references early to ensure they're <clinit>'ed
+        Objects.requireNonNull(Build.IS_USERDEBUG);
+        Objects.requireNonNull(Build.VERSION.SDK);
     }
 
     public static void reset(RavenwoodRule rule) {
@@ -94,9 +130,15 @@
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
+        ActivityManager.reset$ravenwood();
+
         android.os.SystemProperties.reset$ravenwood();
         android.os.Binder.reset$ravenwood();
         android.os.Process.reset$ravenwood();
+
+        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+            maybeThrowPendingUncaughtException(true);
+        }
     }
 
     public static void logTestRunner(String label, Description description) {
@@ -120,4 +162,48 @@
         }
         out.println("-----END ALL THREAD STACKS-----");
     }
+
+    /**
+     * If there's a pending uncaught exception, consume and throw it now. Typically used to
+     * report an exception on a background thread as a failure for the currently running test.
+     */
+    private static void maybeThrowPendingUncaughtException(boolean duringReset) {
+        final Throwable pending = sPendingUncaughtException.getAndSet(null);
+        if (pending != null) {
+            if (duringReset) {
+                throw new IllegalStateException(
+                        "Found an uncaught exception during this test", pending);
+            } else {
+                throw new IllegalStateException(
+                        "Found an uncaught exception before this test started", pending);
+            }
+        }
+    }
+
+    public static void validate(Statement base, Description description,
+            boolean enableOptionalValidation) {
+        validateTestRunner(base, description, enableOptionalValidation);
+    }
+
+    private static void validateTestRunner(Statement base, Description description,
+            boolean shouldFail) {
+        final var testClass = description.getTestClass();
+        final var runWith = testClass.getAnnotation(RunWith.class);
+        if (runWith == null) {
+            return;
+        }
+
+        // Due to build dependencies, we can't directly refer to androidx classes here,
+        // so just check the class name instead.
+        if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
+            var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
+                    + " test runner androidx.test.runner.AndroidJUnit4."
+                    + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
+            if (shouldFail) {
+                Assert.fail(message);
+            } else {
+                System.err.println("Warning: " + message);
+            }
+        }
+    }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 0285b38..764573d 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,10 @@
 
 package android.platform.test.ravenwood;
 
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import static org.junit.Assert.fail;
 
 import android.platform.test.annotations.DisabledOnRavenwood;
@@ -85,6 +89,12 @@
     private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
             !REALLY_DISABLE_PATTERN.pattern().isEmpty();
 
+    /**
+     * If true, enable optional validation on running tests.
+     */
+    private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals(
+            System.getenv("RAVENWOOD_OPTIONAL_VALIDATION"));
+
     static {
         if (ENABLE_PROBE_IGNORED) {
             System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
@@ -94,12 +104,12 @@
         }
     }
 
-    private static final int SYSTEM_UID = 1000;
     private static final int NOBODY_UID = 9999;
-    private static final int FIRST_APPLICATION_UID = 10000;
 
     private static final AtomicInteger sNextPid = new AtomicInteger(100);
 
+    int mCurrentUser = USER_SYSTEM;
+
     /**
      * Unless the test author requests differently, run as "nobody", and give each collection of
      * tests its own unique PID.
@@ -271,6 +281,12 @@
         }
     }
 
+    private void commonPrologue(Statement base, Description description) {
+        RavenwoodRuleImpl.logTestRunner("started", description);
+        RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION);
+        RavenwoodRuleImpl.init(RavenwoodRule.this);
+    }
+
     /**
      * Run the given {@link Statement} with no special treatment.
      */
@@ -280,8 +296,7 @@
             public void evaluate() throws Throwable {
                 Assume.assumeTrue(shouldEnableOnRavenwood(description));
 
-                RavenwoodRuleImpl.logTestRunner("started", description);
-                RavenwoodRuleImpl.init(RavenwoodRule.this);
+                commonPrologue(base, description);
                 try {
                     base.evaluate();
                     RavenwoodRuleImpl.logTestRunner("finished", description);
@@ -306,8 +321,7 @@
             public void evaluate() throws Throwable {
                 Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
 
-                RavenwoodRuleImpl.logTestRunner("started", description);
-                RavenwoodRuleImpl.init(RavenwoodRule.this);
+                commonPrologue(base, description);
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
@@ -327,4 +341,11 @@
             }
         };
     }
+
+    /**
+     * Do not use it outside ravenwood core classes.
+     */
+    public boolean _ravenwood_private$isOptionalValidationEnabled() {
+        return ENABLE_OPTIONAL_VALIDATION;
+    }
 }
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 7d172f2..e951351b 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,6 +17,7 @@
 package android.platform.test.ravenwood;
 
 import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 
 public class RavenwoodRuleImpl {
     public static boolean isOnRavenwood() {
@@ -34,4 +35,8 @@
     public static void logTestRunner(String label, Description description) {
         // No-op when running on a real device
     }
+
+    public static void validate(Statement base, Description description,
+            boolean enableOptionalValidation) {
+    }
 }
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/list-ravenwood-tests.sh
new file mode 100755
index 0000000..fb9b823
--- /dev/null
+++ b/ravenwood/list-ravenwood-tests.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# List all the ravenwood test modules.
+
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
diff --git a/ravenwood/minimum-test/Android.bp b/ravenwood/minimum-test/Android.bp
index bf3583c..e4ed3d5 100644
--- a/ravenwood/minimum-test/Android.bp
+++ b/ravenwood/minimum-test/Android.bp
@@ -13,6 +13,7 @@
 
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
         "androidx.test.rules",
     ],
 
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 7abfecf..03cfad5 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -18,7 +18,7 @@
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Assert;
 import org.junit.Rule;
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index 4135022..a74bca4 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -7,16 +7,6 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-// Ravenwood tests run on the hostside, so we need mockit of the host variant.
-// But we need to use it in modules of the android variant, so we "wash" the variant with it.
-java_host_for_device {
-    name: "mockito_ravenwood",
-    libs: [
-        "mockito",
-        "objenesis",
-    ],
-}
-
 android_ravenwood_test {
     name: "RavenwoodMockitoTest",
 
@@ -26,8 +16,6 @@
     static_libs: [
         "junit",
         "truth",
-
-        "mockito_ravenwood",
     ],
     libs: [
         "android.test.mock",
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 364a86a..1284d64 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.Parcel;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import org.junit.Rule;
@@ -78,4 +79,13 @@
 
         assertThat(object.getPackageName()).isEqualTo("android");
     }
+
+    @Test
+    public void testMockFinalClass() {
+        var object = mock(Parcel.class);
+
+        when(object.readInt()).thenReturn(123);
+
+        assertThat(object.readInt()).isEqualTo(123);
+    }
 }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e49b64e..a5ecd20 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -82,6 +82,8 @@
 android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
 android.os.UserManager
+android.os.VibrationAttributes
+android.os.VibrationAttributes$Builder
 android.os.WorkSource
 
 android.content.ClipData
@@ -144,6 +146,7 @@
 
 android.content.ContentProvider
 
+android.app.ActivityManager
 android.app.Instrumentation
 
 android.metrics.LogMaker
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
new file mode 100755
index 0000000..3f4b8a7
--- /dev/null
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Run all the ravenwood tests.
+
+# "echo" is to remove the newlines
+all_tests=$(echo $(${0%/*}/list-ravenwood-tests.sh) )
+
+echo "Running tests: $all_tests"
+atest $all_tests
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index 5df827f..9179a62 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -101,7 +101,7 @@
 @RunWith(AndroidJUnit4.class)
 public class MyCodeTest {
     @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT);
 
     @Test
     public void testEnabled() {
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 44682e2..f902439 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -52,6 +52,13 @@
 }
 
 flag {
+    name: "fullscreen_fling_gesture"
+    namespace: "accessibility"
+    description: "When true, adds a fling gesture animation for fullscreen magnification"
+    bug: "319175022"
+}
+
+flag {
     name: "pinch_zoom_zero_min_span"
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 63784ba..c58cb72 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -31,7 +31,10 @@
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -140,7 +143,6 @@
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
 import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
 import com.android.internal.accessibility.util.AccessibilityUtils;
@@ -1720,7 +1722,7 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                displayId, ShortcutConstants.UserShortcutType.SOFTWARE, targetName));
+                displayId, ACCESSIBILITY_BUTTON, targetName));
     }
 
     /**
@@ -2199,12 +2201,11 @@
     }
 
     private void showAccessibilityTargetsSelection(int displayId,
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+            @ShortcutType int shortcutType) {
         final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
-        final String chooserClassName =
-                (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
-                        ? AccessibilityShortcutChooserActivity.class.getName()
-                        : AccessibilityButtonChooserActivity.class.getName();
+        final String chooserClassName = (shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
+                ? AccessibilityShortcutChooserActivity.class.getName()
+                : AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
@@ -3275,7 +3276,7 @@
         }
 
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
         if (targetsFromSetting.equals(currentTargets)) {
             return false;
         }
@@ -3291,7 +3292,7 @@
                 userState.mUserId, str -> str, targetsFromSetting);
 
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
         if (targetsFromSetting.equals(currentTargets)) {
             return false;
         }
@@ -3346,7 +3347,7 @@
      */
     private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
         final int lastSize = currentTargets.size();
         if (lastSize == 0) {
             return;
@@ -3531,7 +3532,7 @@
         }
 
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
         final int lastSize = currentTargets.size();
         if (lastSize == 0) {
             return;
@@ -3571,7 +3572,7 @@
             return;
         }
         final Set<String> buttonTargets =
-                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
         int lastSize = buttonTargets.size();
         buttonTargets.removeIf(name -> {
             if (packageName != null && name != null && !name.contains(packageName)) {
@@ -3608,7 +3609,7 @@
         lastSize = buttonTargets.size();
 
         final Set<String> shortcutKeyTargets =
-                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
         userState.mEnabledServices.forEach(componentName -> {
             if (packageName != null && componentName != null
                     && !packageName.equals(componentName.getPackageName())) {
@@ -3665,18 +3666,16 @@
             return;
         }
         final ComponentName serviceName = service.getComponentName();
-        if (userState.removeShortcutTargetLocked(
-                ShortcutConstants.UserShortcutType.HARDWARE, serviceName)) {
+        if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) {
             final Set<String> currentTargets = userState.getShortcutTargetsLocked(
-                    ShortcutConstants.UserShortcutType.HARDWARE);
+                    ACCESSIBILITY_SHORTCUT_KEY);
             persistColonDelimitedSetToSettingLocked(
                     Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                     userState.mUserId, currentTargets, str -> str);
         }
-        if (userState.removeShortcutTargetLocked(
-                ShortcutConstants.UserShortcutType.SOFTWARE, serviceName)) {
+        if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) {
             final Set<String> currentTargets = userState.getShortcutTargetsLocked(
-                    ShortcutConstants.UserShortcutType.SOFTWARE);
+                    ACCESSIBILITY_BUTTON);
             persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                     userState.mUserId, currentTargets, str -> str);
         }
@@ -3752,7 +3751,7 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                Display.DEFAULT_DISPLAY, ShortcutConstants.UserShortcutType.HARDWARE, targetName));
+                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
     }
 
     /**
@@ -3765,7 +3764,7 @@
      *        specified target.
      */
     private void performAccessibilityShortcutInternal(int displayId,
-            @ShortcutConstants.UserShortcutType int shortcutType, @Nullable String targetName) {
+            @ShortcutType int shortcutType, @Nullable String targetName) {
         final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
         if (shortcutTargets.isEmpty()) {
             Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
@@ -3816,7 +3815,7 @@
     }
 
     private boolean performAccessibilityFrameworkFeature(int displayId,
-            ComponentName assignedTarget, @ShortcutConstants.UserShortcutType int shortcutType) {
+            ComponentName assignedTarget, @ShortcutType int shortcutType) {
         final Map<ComponentName, FrameworkFeatureInfo> frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
         if (!frameworkFeatureMap.containsKey(assignedTarget)) {
@@ -3865,12 +3864,12 @@
     /**
      * Perform accessibility service shortcut action.
      *
-     * 1) For {@link ShortcutConstants.UserShortcutType.SOFTWARE} type and services targeting sdk
+     * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk
      *    version <= Q: callbacks to accessibility service if service is bounded and requests
      *    accessibility button.
-     * 2) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
+     * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
      *    version <= Q: turns on / off the accessibility service.
-     * 3) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
+     * 3) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
      *    version > Q and request accessibility button: turn on the accessibility service if it's
      *    not in the enabled state.
      *    (It'll happen when a service is disabled and assigned to shortcut then upgraded.)
@@ -3881,7 +3880,7 @@
      *       button.
      */
     private boolean performAccessibilityShortcutTargetService(int displayId,
-            @ShortcutConstants.UserShortcutType int shortcutType, ComponentName assignedTarget) {
+            @ShortcutType int shortcutType, ComponentName assignedTarget) {
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
             final AccessibilityServiceInfo installedServiceInfo =
@@ -3899,8 +3898,7 @@
             final boolean requestA11yButton = (installedServiceInfo.flags
                     & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             // Turns on / off the accessibility service
-            if ((targetSdk <= Build.VERSION_CODES.Q
-                    && shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
+            if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
                     || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
                 if (serviceConnection == null) {
                     logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
@@ -3914,8 +3912,7 @@
                 }
                 return true;
             }
-            if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE
-                    && targetSdk > Build.VERSION_CODES.Q
+            if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY && targetSdk > Build.VERSION_CODES.Q
                     && requestA11yButton) {
                 if (!userState.getEnabledServicesLocked().contains(assignedTarget)) {
                     enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
@@ -3945,8 +3942,7 @@
     }
 
     @Override
-    public List<String> getAccessibilityShortcutTargets(
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+    public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
                     FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
@@ -3960,13 +3956,12 @@
         return getAccessibilityShortcutTargetsInternal(shortcutType);
     }
 
-    private List<String> getAccessibilityShortcutTargetsInternal(
-            @ShortcutConstants.UserShortcutType int shortcutType) {
+    private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) {
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
             final ArrayList<String> shortcutTargets = new ArrayList<>(
                     userState.getShortcutTargetsLocked(shortcutType));
-            if (shortcutType != ShortcutConstants.UserShortcutType.SOFTWARE) {
+            if (shortcutType != ACCESSIBILITY_BUTTON) {
                 return shortcutTargets;
             }
             // Adds legacy a11y services requesting a11y button into the list.
@@ -4429,7 +4424,7 @@
             }
         }
         // Warning is not required if the service is already assigned to a shortcut.
-        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+        for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) {
             if (getAccessibilityShortcutTargets(shortcutType).contains(
                     componentName.flattenToString())) {
                 return false;
@@ -4609,8 +4604,8 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
-        new AccessibilityShellCommand(this, mSystemActionPerformer)
-                .exec(this, in, out, err, args, callback, resultReceiver);
+        new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
+                callback, resultReceiver);
     }
 
     private final class InteractionBridge {
@@ -5725,10 +5720,10 @@
     }
 
     @Override
-    public void attachAccessibilityOverlayToDisplay_enforcePermission(
+    public void attachAccessibilityOverlayToDisplay(
             int displayId, SurfaceControl sc) {
         mContext.enforceCallingPermission(
-                INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay_enforcePermission");
+                INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
         mMainHandler.sendMessage(
                 obtainMessage(
                         AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 41165b6..68ee780 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -24,6 +24,9 @@
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 
@@ -48,7 +51,6 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.accessibility.common.ShortcutConstants;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -755,21 +757,13 @@
      * @param shortcutType The shortcut type.
      * @return The array set of the strings
      */
-    public ArraySet<String> getShortcutTargetsLocked(
-            @ShortcutConstants.UserShortcutType int shortcutType) {
-        if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE) {
+    public ArraySet<String> getShortcutTargetsLocked(@ShortcutType int shortcutType) {
+        if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
             return mAccessibilityShortcutKeyTargets;
-        } else if (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE) {
+        } else if (shortcutType == ACCESSIBILITY_BUTTON) {
             return mAccessibilityButtonTargets;
-        } else if ((shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
-                && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
-                shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP
-                        && isMagnificationTwoFingerTripleTapEnabledLocked())) {
-            ArraySet<String> targets = new ArraySet<>();
-            targets.add(MAGNIFICATION_CONTROLLER_NAME);
-            return targets;
         }
-        return new ArraySet<>();
+        return null;
     }
 
     /**
@@ -808,22 +802,12 @@
     /**
      * Removes given shortcut target in the list.
      *
-     * @param shortcutType one of {@link ShortcutConstants.UserShortcutType.HARDWARE} or
-     *                     {@link ShortcutConstants.UserShortcutType.SOFTWARE}. Other types are not
-     *                     implemented.
-     * @param target       The component name of the shortcut target.
+     * @param shortcutType The shortcut type.
+     * @param target The component name of the shortcut target.
      * @return true if the shortcut target is removed.
      */
-    public boolean removeShortcutTargetLocked(@ShortcutConstants.UserShortcutType int shortcutType,
+    public boolean removeShortcutTargetLocked(@ShortcutType int shortcutType,
             ComponentName target) {
-        if (shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
-                || shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP) {
-            throw new UnsupportedOperationException(
-                    "removeShortcutTargetLocked only support shortcut type: "
-                            + "software and hardware for now"
-            );
-        }
-
         return getShortcutTargetsLocked(shortcutType).removeIf(name -> {
             ComponentName componentName;
             if (name == null
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 78f07e4..a11cf8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -818,7 +818,7 @@
             }
 
             // Don't need to add the embedded hierarchy windows into the accessibility windows list.
-            if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
+            if (isEmbeddedHierarchyWindowsLocked(windowId)) {
                 return null;
             }
             final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
@@ -866,21 +866,6 @@
             return reportedWindow;
         }
 
-        private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
-            final IBinder leashToken = mWindowIdMap.get(windowId);
-            if (leashToken == null) {
-                return false;
-            }
-
-            for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
-                if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         private int getTypeForWindowManagerWindowType(int windowType) {
             switch (windowType) {
                 case WindowManager.LayoutParams.TYPE_APPLICATION:
@@ -1490,7 +1475,7 @@
      * @return The windowId of the parent window, or self if no parent exists
      */
     public int resolveParentWindowIdLocked(int windowId) {
-        final IBinder token = getTokenLocked(windowId);
+        final IBinder token = getLeashTokenLocked(windowId);
         if (token == null) {
             return windowId;
         }
@@ -2095,7 +2080,7 @@
      * @param windowId The windowID.
      * @return The token, or {@code NULL} if this windowID doesn't exist
      */
-    IBinder getTokenLocked(int windowId) {
+    IBinder getLeashTokenLocked(int windowId) {
         return mWindowIdMap.get(windowId);
     }
 
@@ -2124,6 +2109,23 @@
     }
 
     /**
+     * Checks if the window is embedded into another window so that the window should be excluded
+     * from the exposed accessibility windows, and the node tree should be embedded in the host.
+     */
+    boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
+        if (mHostEmbeddedMap.size() == 0) {
+            return false;
+        }
+
+        final IBinder leashToken = getLeashTokenLocked(windowId);
+        if (leashToken == null) {
+            return false;
+        }
+
+        return mHostEmbeddedMap.containsKey(leashToken);
+    }
+
+    /**
      * Checks if the window belongs to a proxy display and if so clears the focused window id.
      * @param focusClearedWindowId the cleared window id.
      * @return true if an observer is proxy-ed and has cleared its focused window id.
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 805f6e3..351760b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -25,7 +25,9 @@
 
 import android.accessibilityservice.MagnificationConfig;
 import android.animation.Animator;
+import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -51,6 +53,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.MagnificationAnimationCallback;
 import android.view.animation.DecelerateInterpolator;
+import android.widget.Scroller;
 
 import com.android.internal.R;
 import com.android.internal.accessibility.common.MagnificationConstants;
@@ -60,6 +63,7 @@
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.ArrayList;
@@ -86,6 +90,8 @@
     private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
 
     private final Object mLock;
+    private final Supplier<Scroller> mScrollerSupplier;
+    private final Supplier<TimeAnimator> mTimeAnimatorSupplier;
 
     private final ControllerContext mControllerCtx;
 
@@ -149,7 +155,13 @@
 
         DisplayMagnification(int displayId) {
             mDisplayId = displayId;
-            mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
+            mSpecAnimationBridge =
+                    new SpecAnimationBridge(
+                            mControllerCtx,
+                            mLock,
+                            mDisplayId,
+                            mScrollerSupplier,
+                            mTimeAnimatorSupplier);
         }
 
         /**
@@ -406,6 +418,52 @@
             }
         }
 
+        void startFlingAnimation(
+                float xPixelsPerSecond,
+                float yPixelsPerSecond,
+                MagnificationAnimationCallback animationCallback
+        ) {
+            if (DEBUG) {
+                Slog.i(LOG_TAG,
+                        "startFlingAnimation(spec = " + xPixelsPerSecond + ", animationCallback = "
+                                + animationCallback + ")");
+            }
+            if (Thread.currentThread().getId() == mMainThreadId) {
+                mSpecAnimationBridge.startFlingAnimation(
+                        xPixelsPerSecond,
+                        yPixelsPerSecond,
+                        getMinOffsetXLocked(),
+                        getMaxOffsetXLocked(),
+                        getMinOffsetYLocked(),
+                        getMaxOffsetYLocked(),
+                        animationCallback);
+            } else {
+                final Message m =
+                        PooledLambda.obtainMessage(
+                                SpecAnimationBridge::startFlingAnimation,
+                                mSpecAnimationBridge,
+                                xPixelsPerSecond,
+                                yPixelsPerSecond,
+                                getMinOffsetXLocked(),
+                                getMaxOffsetXLocked(),
+                                getMinOffsetYLocked(),
+                                getMaxOffsetYLocked(),
+                                animationCallback);
+                mControllerCtx.getHandler().sendMessage(m);
+            }
+        }
+
+        void cancelFlingAnimation() {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "cancelFlingAnimation()");
+            }
+            if (Thread.currentThread().getId() == mMainThreadId) {
+                mSpecAnimationBridge.cancelFlingAnimation();
+            } else {
+                mControllerCtx.getHandler().post(mSpecAnimationBridge::cancelFlingAnimation);
+            }
+        }
+
         /**
          * Get the ID of the last service that changed the magnification spec.
          *
@@ -759,6 +817,63 @@
             sendSpecToAnimation(mCurrentMagnificationSpec, null);
         }
 
+        @GuardedBy("mLock")
+        void startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id) {
+            if (!mRegistered) {
+                return;
+            }
+            if (!isActivated()) {
+                return;
+            }
+
+            if (id != INVALID_SERVICE_ID) {
+                mIdOfLastServiceToMagnify = id;
+            }
+
+            startFlingAnimation(
+                    xPixelsPerSecond,
+                    yPixelsPerSecond,
+                    new MagnificationAnimationCallback() {
+                        @Override
+                        public void onResult(boolean success) {
+                            // never called
+                        }
+
+                        @Override
+                        public void onResult(boolean success, MagnificationSpec lastSpecSent) {
+                            if (DEBUG) {
+                                Slog.i(
+                                        LOG_TAG,
+                                        "startFlingAnimation finished( "
+                                                + success
+                                                + " = "
+                                                + lastSpecSent.offsetX
+                                                + ", "
+                                                + lastSpecSent.offsetY
+                                                + ")");
+                            }
+                            synchronized (mLock) {
+                                mCurrentMagnificationSpec.setTo(lastSpecSent);
+                                onMagnificationChangedLocked();
+                            }
+                        }
+                    });
+        }
+
+
+        @GuardedBy("mLock")
+        void cancelFling(int id) {
+            if (!mRegistered) {
+                return;
+            }
+
+            if (id != INVALID_SERVICE_ID) {
+                mIdOfLastServiceToMagnify = id;
+            }
+
+            cancelFlingAnimation();
+        }
+
         boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
             if (DEBUG) {
                 Slog.i(LOG_TAG,
@@ -838,7 +953,9 @@
                 magnificationInfoChangedCallback,
                 scaleProvider,
                 /* thumbnailSupplier= */ null,
-                backgroundExecutor);
+                backgroundExecutor,
+                () -> new Scroller(context),
+                TimeAnimator::new);
     }
 
     /** Constructor for tests */
@@ -849,9 +966,13 @@
             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
             @NonNull MagnificationScaleProvider scaleProvider,
             Supplier<MagnificationThumbnail> thumbnailSupplier,
-            @NonNull Executor backgroundExecutor) {
+            @NonNull Executor backgroundExecutor,
+            Supplier<Scroller> scrollerSupplier,
+            Supplier<TimeAnimator> timeAnimatorSupplier) {
         mControllerCtx = ctx;
         mLock = lock;
+        mScrollerSupplier = scrollerSupplier;
+        mTimeAnimatorSupplier = timeAnimatorSupplier;
         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
         mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
         addInfoChangedCallback(magnificationInfoChangedCallback);
@@ -1437,6 +1558,42 @@
     }
 
     /**
+     * Call after a pan ends, if the velocity has passed the threshold, to start a fling animation.
+     *
+     * @param displayId The logical display id.
+     * @param xPixelsPerSecond the velocity of the last pan gesture in the X direction, in current
+     *     screen pixels per second.
+     * @param yPixelsPerSecond the velocity of the last pan gesture in the Y direction, in current
+     *     screen pixels per second.
+     * @param id the ID of the service requesting the change
+     */
+    public void startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.startFling(xPixelsPerSecond, yPixelsPerSecond, id);
+        }
+    }
+
+    /**
+     * Call to cancel the fling animation if it is running. Call this on any ACTION_DOWN event.
+     *
+     * @param displayId The logical display id.
+     * @param id the ID of the service requesting the change
+     */
+    public void cancelFling(int displayId, int id) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.cancelFling(id);
+        }
+    }
+
+    /**
      * Get the ID of the last service that changed the magnification spec.
      *
      * @param displayId The logical display id.
@@ -1698,7 +1855,15 @@
         @GuardedBy("mLock")
         private boolean mEnabled = false;
 
-        private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+        private final Scroller mScroller;
+        private final TimeAnimator mScrollAnimator;
+
+        private SpecAnimationBridge(
+                ControllerContext ctx,
+                Object lock,
+                int displayId,
+                Supplier<Scroller> scrollerSupplier,
+                Supplier<TimeAnimator> timeAnimatorSupplier) {
             mControllerCtx = ctx;
             mLock = lock;
             mDisplayId = displayId;
@@ -1709,6 +1874,37 @@
             mValueAnimator.setFloatValues(0.0f, 1.0f);
             mValueAnimator.addUpdateListener(this);
             mValueAnimator.addListener(this);
+
+            if (Flags.fullscreenFlingGesture()) {
+                mScroller = scrollerSupplier.get();
+                mScrollAnimator = timeAnimatorSupplier.get();
+                mScrollAnimator.addListener(this);
+                mScrollAnimator.setTimeListener(
+                        (animation, totalTime, deltaTime) -> {
+                            synchronized (mLock) {
+                                if (DEBUG) {
+                                    Slog.v(
+                                            LOG_TAG,
+                                            "onScrollAnimationUpdate: "
+                                                    + mEnabled + " : " + totalTime);
+                                }
+
+                                if (mEnabled) {
+                                    if (!mScroller.computeScrollOffset()) {
+                                        animation.end();
+                                        return;
+                                    }
+
+                                    mEndMagnificationSpec.offsetX = mScroller.getCurrX();
+                                    mEndMagnificationSpec.offsetY = mScroller.getCurrY();
+                                    setMagnificationSpecLocked(mEndMagnificationSpec);
+                                }
+                            }
+                        });
+            } else {
+                mScroller = null;
+                mScrollAnimator = null;
+            }
         }
 
         /**
@@ -1735,16 +1931,20 @@
             }
         }
 
-        void updateSentSpecMainThread(MagnificationSpec spec,
-                MagnificationAnimationCallback animationCallback) {
-            if (mValueAnimator.isRunning()) {
-                mValueAnimator.cancel();
-            }
+        @MainThread
+        void updateSentSpecMainThread(
+                MagnificationSpec spec, MagnificationAnimationCallback animationCallback) {
+            cancelAnimations();
 
             mAnimationCallback = animationCallback;
             // If the current and sent specs don't match, update the sent spec.
             synchronized (mLock) {
                 final boolean changed = !mSentMagnificationSpec.equals(spec);
+                if (DEBUG_SET_MAGNIFICATION_SPEC) {
+                    Slog.d(
+                            LOG_TAG,
+                            "updateSentSpecMainThread: " + mEnabled + " : changed: " + changed);
+                }
                 if (changed) {
                     if (mAnimationCallback != null) {
                         animateMagnificationSpecLocked(spec);
@@ -1757,12 +1957,13 @@
             }
         }
 
+        @MainThread
         private void sendEndCallbackMainThread(boolean success) {
             if (mAnimationCallback != null) {
                 if (DEBUG) {
                     Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success);
                 }
-                mAnimationCallback.onResult(success);
+                mAnimationCallback.onResult(success, mSentMagnificationSpec);
                 mAnimationCallback = null;
             }
         }
@@ -1830,6 +2031,77 @@
         public void onAnimationRepeat(Animator animation) {
 
         }
+
+        /**
+         * Call after a pan ends, if the velocity has passed the threshold, to start a fling
+         * animation.
+         */
+        @MainThread
+        public void startFlingAnimation(
+                float xPixelsPerSecond,
+                float yPixelsPerSecond,
+                float minX,
+                float maxX,
+                float minY,
+                float maxY,
+                MagnificationAnimationCallback animationCallback
+        ) {
+            if (!Flags.fullscreenFlingGesture()) {
+                return;
+            }
+            cancelAnimations();
+
+            mAnimationCallback = animationCallback;
+
+            // We use this as a temp object to send updates every animation frame, so make sure it
+            // matches the current spec before we start.
+            mEndMagnificationSpec.setTo(mSentMagnificationSpec);
+
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "startFlingAnimation: "
+                        + "offsetX " + mSentMagnificationSpec.offsetX
+                        + "offsetY " + mSentMagnificationSpec.offsetY
+                        + "xPixelsPerSecond " + xPixelsPerSecond
+                        + "yPixelsPerSecond " + yPixelsPerSecond
+                        + "minX " + minX
+                        + "maxX " + maxX
+                        + "minY " + minY
+                        + "maxY " + maxY
+                );
+            }
+
+            mScroller.fling(
+                    (int) mSentMagnificationSpec.offsetX,
+                    (int) mSentMagnificationSpec.offsetY,
+                    (int) xPixelsPerSecond,
+                    (int) yPixelsPerSecond,
+                    (int) minX,
+                    (int) maxX,
+                    (int) minY,
+                    (int) maxY);
+
+            mScrollAnimator.start();
+        }
+
+        @MainThread
+        void cancelAnimations() {
+            if (mValueAnimator.isRunning()) {
+                mValueAnimator.cancel();
+            }
+
+            cancelFlingAnimation();
+        }
+
+        @MainThread
+        void cancelFlingAnimation() {
+            if (!Flags.fullscreenFlingGesture()) {
+                return;
+            }
+            if (mScrollAnimator.isRunning()) {
+                mScrollAnimator.cancel();
+            }
+            mScroller.forceFinished(true);
+        }
     }
 
     private static class ScreenStateObserver extends BroadcastReceiver {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index baae1d93..f4ea754 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -62,6 +62,7 @@
 import android.view.MotionEvent.PointerProperties;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import com.android.internal.R;
@@ -174,6 +175,10 @@
 
     private final boolean mIsWatch;
 
+    @Nullable private VelocityTracker mVelocityTracker;
+    private final int mMinimumVelocity;
+    private final int mMaximumVelocity;
+
     public FullScreenMagnificationGestureHandler(@UiContext Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
             AccessibilityTraceManager trace,
@@ -184,15 +189,25 @@
             @NonNull WindowMagnificationPromptController promptController,
             int displayId,
             FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
-        this(context, fullScreenMagnificationController, trace, callback,
-                detectSingleFingerTripleTap, detectTwoFingerTripleTap,
-                detectShortcutTrigger, promptController, displayId,
-                fullScreenMagnificationVibrationHelper, /* magnificationLogger= */ null);
+        this(
+                context,
+                fullScreenMagnificationController,
+                trace,
+                callback,
+                detectSingleFingerTripleTap,
+                detectTwoFingerTripleTap,
+                detectShortcutTrigger,
+                promptController,
+                displayId,
+                fullScreenMagnificationVibrationHelper,
+                /* magnificationLogger= */ null,
+                ViewConfiguration.get(context));
     }
 
     /** Constructor for tests. */
     @VisibleForTesting
-    FullScreenMagnificationGestureHandler(@UiContext Context context,
+    FullScreenMagnificationGestureHandler(
+            @UiContext Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
             AccessibilityTraceManager trace,
             Callback callback,
@@ -202,7 +217,8 @@
             @NonNull WindowMagnificationPromptController promptController,
             int displayId,
             FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
-            MagnificationLogger magnificationLogger) {
+            MagnificationLogger magnificationLogger,
+            ViewConfiguration viewConfiguration) {
         super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
                 detectShortcutTrigger, trace, callback);
         if (DEBUG_ALL) {
@@ -212,6 +228,15 @@
                             + ", detectTwoFingerTripleTap = " + detectTwoFingerTripleTap
                             + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
         }
+
+        if (Flags.fullscreenFlingGesture()) {
+            mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
+            mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
+        } else {
+            mMinimumVelocity = 0;
+            mMaximumVelocity = 0;
+        }
+
         mFullScreenMagnificationController = fullScreenMagnificationController;
         mMagnificationInfoChangedCallback =
                 new FullScreenMagnificationController.MagnificationInfoChangedCallback() {
@@ -299,6 +324,10 @@
 
     @Override
     void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (event.getActionMasked() == ACTION_DOWN) {
+            cancelFling();
+        }
+
         handleEventWith(mCurrentState, event, rawEvent, policyFlags);
     }
 
@@ -501,6 +530,7 @@
                 }
                 persistScaleAndTransitionTo(mViewportDraggingState);
             } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+                onPanningFinished(event);
                 // if feature flag is enabled, currently only true on watches
                 if (mIsSinglePanningEnabled) {
                     mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
@@ -578,6 +608,7 @@
                 Slog.i(mLogTag, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
+            onPan(second);
             mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                     distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             if (mIsSinglePanningEnabled) {
@@ -973,7 +1004,7 @@
                                     && overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
-                            }
+                            } // TODO(b/319537921): should there be an else here?
                             //Primary pointer is swiping, so transit to PanningScalingState
                             transitToPanningScalingStateAndClear();
                         } else if (mIsSinglePanningEnabled
@@ -982,7 +1013,7 @@
                             if (overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
-                            }
+                            } // TODO(b/319537921): should there be an else here?
                             transitToSinglePanningStateAndClear();
                         } else if (!mIsTwoFingerCountReached) {
                             // If it is a two-finger gesture, do not transition to the
@@ -1742,6 +1773,71 @@
         }
     }
 
+    /** Call during MOVE events for a panning gesture. */
+    private void onPan(MotionEvent event) {
+        if (!Flags.fullscreenFlingGesture()) {
+            return;
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(event);
+    }
+
+    /**
+     * Call during UP events for a panning gesture, so we can detect a fling and play a physics-
+     * based fling animation.
+     */
+    private void onPanningFinished(MotionEvent event) {
+        if (!Flags.fullscreenFlingGesture()) {
+            return;
+        }
+
+        if (mVelocityTracker == null) {
+            Log.e(mLogTag, "onPanningFinished: mVelocityTracker is null");
+            return;
+        }
+        mVelocityTracker.addMovement(event);
+        mVelocityTracker.computeCurrentVelocity(/* units= */ 1000, mMaximumVelocity);
+
+        float xPixelsPerSecond = mVelocityTracker.getXVelocity();
+        float yPixelsPerSecond = mVelocityTracker.getYVelocity();
+
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+
+        if (DEBUG_PANNING_SCALING) {
+            Slog.v(
+                    mLogTag,
+                    "onPanningFinished: pixelsPerSecond: "
+                            + xPixelsPerSecond
+                            + ", "
+                            + yPixelsPerSecond
+                            + " mMinimumVelocity: "
+                            + mMinimumVelocity);
+        }
+
+        if ((Math.abs(yPixelsPerSecond) > mMinimumVelocity)
+                || (Math.abs(xPixelsPerSecond) > mMinimumVelocity)) {
+            mFullScreenMagnificationController.startFling(
+                    mDisplayId,
+                    xPixelsPerSecond,
+                    yPixelsPerSecond,
+                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+        }
+    }
+
+    private void cancelFling() {
+        if (!Flags.fullscreenFlingGesture()) {
+            return;
+        }
+
+        mFullScreenMagnificationController.cancelFling(
+                    mDisplayId,
+                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+    }
+
     final class SinglePanningState extends SimpleOnGestureListener implements State {
 
 
@@ -1756,6 +1852,8 @@
             int action = event.getActionMasked();
             switch (action) {
                 case ACTION_UP:
+                    onPanningFinished(event);
+                    // fall-through!
                 case ACTION_CANCEL:
                     mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
                     mOverscrollHandler.clearEdgeState();
@@ -1770,6 +1868,7 @@
             if (mCurrentState != mSinglePanningState) {
                 return true;
             }
+            onPan(second);
             mFullScreenMagnificationController.offsetMagnifiedRegion(
                     mDisplayId,
                     distanceX,
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 727721d..532db12 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -12,4 +12,11 @@
     namespace: "autofill"
     description: "Guards against Autofill-Credman integration phase 2"
     bug: "320730001"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "autofill_credman_dev_integration"
+    namespace: "autofill"
+    description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
+    bug: "320730001"
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index cf414d1..3645c40 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -254,6 +254,14 @@
         mEventInternal.ifPresent(event -> event.mIsCredentialRequest = isCredentialRequest);
     }
 
+    /**
+     * Set webview_requested_credential
+     */
+    public void maybeSetWebviewRequestedCredential(boolean webviewRequestedCredential) {
+        mEventInternal.ifPresent(event ->
+                event.mWebviewRequestedCredential = webviewRequestedCredential);
+    }
+
     public void maybeSetNoPresentationEventReason(@NotShownReason int reason) {
         mEventInternal.ifPresent(event -> {
             if (event.mCountShown == 0) {
@@ -578,7 +586,8 @@
                     + " mDetectionPreference=" + event.mDetectionPreference
                     + " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
                     + " mAppPackageUid=" + mCallingAppUid
-                    + " mIsCredentialRequest=" + event.mIsCredentialRequest);
+                    + " mIsCredentialRequest=" + event.mIsCredentialRequest
+                    + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -618,7 +627,8 @@
                 event.mDetectionPreference,
                 event.mFieldClassificationRequestId,
                 mCallingAppUid,
-                event.mIsCredentialRequest);
+                event.mIsCredentialRequest,
+                event.mWebviewRequestedCredential);
         mEventInternal = Optional.empty();
     }
 
@@ -653,6 +663,7 @@
         @DetectionPreference int mDetectionPreference = DETECTION_PREFER_UNKNOWN;
         int mFieldClassificationRequestId = -1;
         boolean mIsCredentialRequest = false;
+        boolean mWebviewRequestedCredential = false;
 
         PresentationStatsEventInternal() {}
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 049feee..83d9cdb 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -18,6 +18,7 @@
 
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.AutofillService.WEBVIEW_REQUESTED_CREDENTIAL_KEY;
 import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
 import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
 import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
@@ -5516,8 +5517,11 @@
         mResponses.put(requestId, newResponse);
         mClientState = newClientState != null ? newClientState : newResponse.getClientState();
 
+        boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean(
+                WEBVIEW_REQUESTED_CREDENTIAL_KEY, false);
         List<Dataset> datasetList = newResponse.getDatasets();
 
+        mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman);
         mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(sIdCounterForPcc.get());
         mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId);
         mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList);
diff --git a/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
new file mode 100644
index 0000000..69c7b29
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
@@ -0,0 +1,59 @@
+/*
+ * 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.autofill.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.ButtonBarLayout;
+
+/** An extension of {@link ButtonBarLayout} for use in Autofill bottom sheets. */
+public class BottomSheetButtonBarLayout extends ButtonBarLayout {
+
+    public BottomSheetButtonBarLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final View spacer = findViewById(R.id.autofill_button_bar_spacer);
+        if (spacer == null) {
+            return;
+        }
+        if (isStacked()) {
+            spacer.getLayoutParams().width = 0;
+            spacer.getLayoutParams().height =
+                    getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_height);
+            setGravity(Gravity.CENTER_VERTICAL | Gravity.END);
+        } else {
+            spacer.getLayoutParams().width =
+                    getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_width);
+            spacer.getLayoutParams().height = 0;
+            setGravity(Gravity.CENTER_VERTICAL);
+        }
+    }
+
+    private boolean isStacked() {
+        return getOrientation() == LinearLayout.VERTICAL;
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index b2716ec..d580f3a 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.autofill.ui;
 
+import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
 import static com.android.server.autofill.Helper.paramsToString;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -31,6 +32,7 @@
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetFieldFilter;
 import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
 import android.text.TextUtils;
 import android.util.PluralsMessageFormatter;
 import android.util.Slog;
@@ -79,6 +81,7 @@
             com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
     private static final int THEME_ID_DARK =
             com.android.internal.R.style.Theme_DeviceDefault_Autofill;
+    private static final int AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS = 5;
 
     private static final TypedValue sTempTypedValue = new TypedValue();
 
@@ -211,7 +214,11 @@
             if (sVerbose) {
                 Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
             }
-        } else {
+        } else if (Flags.autofillCredmanIntegration() && (
+                (response.getFlags() & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0)) {
+            mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
+        }
+        else {
             mVisibleDatasetsMaxCount = mContext.getResources()
                     .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
         }
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 6a63b3a..71f2b9e 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -32,4 +32,13 @@
     description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
     bug: "320633449"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_increase_datatypes_for_agent_logging"
+    namespace: "onboarding"
+    description: "Increase the number of a supported datatypes that an agent can define for its "
+            "logger."
+    bug: "296844513"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 586aa8a..af0777c 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.companion;
 
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -25,8 +27,10 @@
 import android.companion.DevicePresenceEvent;
 import android.content.ComponentName;
 import android.content.Context;
+import android.hardware.power.Mode;
 import android.os.Handler;
 import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -79,6 +83,8 @@
     private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
 
+    private final PowerManagerInternal mPowerManagerInternal;
+
     @GuardedBy("mBoundCompanionApplications")
     private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
             mBoundCompanionApplications;
@@ -87,11 +93,13 @@
 
     CompanionApplicationController(Context context, AssociationStore associationStore,
             ObservableUuidStore observableUuidStore,
-            CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
+            CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
+            PowerManagerInternal powerManagerInternal) {
         mContext = context;
         mAssociationStore = associationStore;
         mObservableUuidStore =  observableUuidStore;
         mDevicePresenceMonitor = companionDevicePresenceMonitor;
+        mPowerManagerInternal = powerManagerInternal;
         mCompanionServicesRegister = new CompanionServicesRegister();
         mBoundCompanionApplications = new AndroidPackageMap<>();
         mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
@@ -364,9 +372,21 @@
         boolean isPrimary = serviceConnector.isPrimary();
         Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
 
-        // First: Only mark not BOUND for primary service.
-        synchronized (mBoundCompanionApplications) {
-            if (serviceConnector.isPrimary()) {
+        // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+        if (isPrimary) {
+            final List<AssociationInfo> associations =
+                    mAssociationStore.getAssociationsForPackage(userId, packageName);
+
+            for (AssociationInfo association : associations) {
+                final String deviceProfile = association.getDeviceProfile();
+                if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+                    Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+                    mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+                    break;
+                }
+            }
+
+            synchronized (mBoundCompanionApplications) {
                 mBoundCompanionApplications.removePackage(userId, packageName);
             }
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 5019428..0054bc8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,7 +20,6 @@
 import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
 import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
 import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
@@ -270,7 +269,8 @@
                 mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
 
         mCompanionAppController = new CompanionApplicationController(
-                context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor);
+                context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
+                mPowerManagerInternal);
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                 mPackageManagerInternal, mAssociationStore,
@@ -1128,7 +1128,9 @@
 
             mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
 
-            if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+            final String deviceProfile = association.getDeviceProfile();
+            if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+                Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
                 mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
             }
         }
@@ -1146,7 +1148,9 @@
 
             mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
 
-            if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+            final String deviceProfile = association.getDeviceProfile();
+            if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+                Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
                 mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
             }
         }
diff --git a/services/contextualsearch/OWNERS b/services/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/services/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ea1b0f5..e7fae24 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -48,6 +48,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -3597,6 +3598,13 @@
         return mInternalStorageSize;
     }
 
+    @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @Override
+    public int getInternalStorageRemainingLifetime() throws RemoteException {
+        super.getInternalStorageRemainingLifetime_enforcePermission();
+        return mVold.getStorageRemainingLifetime();
+    }
+
     /**
      * Enforces that the caller is the {@link ExternalStorageService}
      *
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a493d7a..a341b4a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -24,6 +24,8 @@
 import android.content.ComponentName;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SignedPackage;
 import android.os.Build;
 import android.os.CarrierAssociatedAppEntry;
 import android.os.Environment;
@@ -350,6 +352,16 @@
     // updated to avoid cached/potentially tampered results.
     private final Set<String> mPreinstallPackagesWithStrictSignatureCheck = new ArraySet<>();
 
+    // A set of packages that should be considered "trusted packages" by ECM (Enhanced
+    // Confirmation Mode). "Trusted packages" are exempt from ECM (i.e., they will never be
+    // considered "restricted").
+    private final ArraySet<SignedPackage> mEnhancedConfirmationTrustedPackages = new ArraySet<>();
+
+    // A set of packages that should be considered "trusted installers" by ECM (Enhanced
+    // Confirmation Mode). "Trusted installers", and all apps installed by a trusted installer, are
+    // exempt from ECM (i.e., they will never be considered "restricted").
+    private final ArraySet<SignedPackage> mEnhancedConfirmationTrustedInstallers = new ArraySet<>();
+
     /**
      * Map of system pre-defined, uniquely named actors; keys are namespace,
      * value maps actor name to package name.
@@ -560,6 +572,14 @@
         return mPreinstallPackagesWithStrictSignatureCheck;
     }
 
+    public ArraySet<SignedPackage> getEnhancedConfirmationTrustedPackages() {
+        return mEnhancedConfirmationTrustedPackages;
+    }
+
+    public ArraySet<SignedPackage> getEnhancedConfirmationTrustedInstallers() {
+        return mEnhancedConfirmationTrustedInstallers;
+    }
+
     /**
      * Only use for testing. Do NOT use in production code.
      * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1558,6 +1578,26 @@
                             }
                         }
                     } break;
+                    case "enhanced-confirmation-trusted-package": {
+                        if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+                            SignedPackage signedPackage = parseEnhancedConfirmationTrustedPackage(
+                                    parser, permFile, name);
+                            if (signedPackage != null) {
+                                mEnhancedConfirmationTrustedPackages.add(signedPackage);
+                            }
+                            break;
+                        }
+                    } // fall through if flag is not enabled
+                    case "enhanced-confirmation-trusted-installer": {
+                        if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+                            SignedPackage signedPackage = parseEnhancedConfirmationTrustedPackage(
+                                    parser, permFile, name);
+                            if (signedPackage != null) {
+                                mEnhancedConfirmationTrustedInstallers.add(signedPackage);
+                            }
+                            break;
+                        }
+                    } // fall through if flag is not enabled
                     default: {
                         Slog.w(TAG, "Tag " + name + " is unknown in "
                                 + permFile + " at " + parser.getPositionDescription());
@@ -1619,6 +1659,33 @@
         }
     }
 
+    private @Nullable SignedPackage parseEnhancedConfirmationTrustedPackage(XmlPullParser parser,
+            File permFile, String elementName) {
+        String pkgName = parser.getAttributeValue(null, "package");
+        if (TextUtils.isEmpty(pkgName)) {
+            Slog.w(TAG, "<" + elementName + "> without package " + permFile + " at "
+                    + parser.getPositionDescription());
+            return null;
+        }
+
+        String certificateDigestStr = parser.getAttributeValue(null, "sha256-cert-digest");
+        if (TextUtils.isEmpty(certificateDigestStr)) {
+            Slog.w(TAG, "<" + elementName + "> without sha256-cert-digest in " + permFile
+                    + " at " + parser.getPositionDescription());
+            return null;
+        }
+        byte[] certificateDigest = null;
+        try {
+            certificateDigest = new Signature(certificateDigestStr.replace(":", "")).toByteArray();
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "<" + elementName + "> with invalid sha256-cert-digest in "
+                    + permFile + " at " + parser.getPositionDescription());
+            return null;
+        }
+
+        return new SignedPackage(pkgName, certificateDigest);
+    }
+
     // This method only enables a new Android feature added in U and will not have impact on app
     // compatibility
     @SuppressWarnings("AndroidFrameworkCompatChange")
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cd8be33..82d9377 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.app.Flags.modesApi;
+import static android.app.Flags.enableNightModeCache;
 import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -135,7 +136,23 @@
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
-    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+    private final NightMode mNightMode = new NightMode(){
+        private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
+
+        @Override
+        public int get() {
+            return mNightModeValue;
+        }
+
+        @Override
+        public void set(int mode) {
+            mNightModeValue = mode;
+            if (enableNightModeCache()) {
+                UiModeManager.invalidateNightModeCache();
+            }
+        }
+    };
     private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
     private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
     private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
@@ -297,7 +314,7 @@
         @Override
         public void onTwilightStateChanged(@Nullable TwilightState state) {
             synchronized (mLock) {
-                if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
+                if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
                     if (shouldApplyAutomaticChangesImmediately()) {
                         updateLocked(0, 0);
                     } else {
@@ -392,7 +409,7 @@
 
     private void updateSystemProperties() {
         int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
-                mNightMode, 0);
+                mNightMode.get(), 0);
         if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
             mode = MODE_NIGHT_YES;
         }
@@ -412,7 +429,7 @@
     @Override
     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         mCurrentUser = to.getUserIdentifier();
-        if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
+        if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
         getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
         verifySetupWizardCompleted();
         synchronized (mLock) {
@@ -471,12 +488,12 @@
         verifySetupWizardCompleted();
 
         final Resources res = context.getResources();
+        mNightMode.set(res.getInteger(
+                com.android.internal.R.integer.config_defaultNightMode));
         mStartDreamImmediatelyOnDock = res.getBoolean(
                 com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
         mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
                 com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
-        mNightMode = res.getInteger(
-                com.android.internal.R.integer.config_defaultNightMode);
         mDefaultUiModeType = res.getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mCarModeKeepsScreenOn = (res.getInteger(
@@ -510,7 +527,7 @@
     private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (mNightMode == MODE_NIGHT_AUTO) {
+            if (mNightMode.get() == MODE_NIGHT_AUTO) {
                 persistComputedNightMode(mCurrentUser);
             }
         }
@@ -585,7 +602,7 @@
     }
 
     private void updateCustomTimeLocked() {
-        if (mNightMode != MODE_NIGHT_CUSTOM) return;
+        if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
         if (shouldApplyAutomaticChangesImmediately()) {
             updateLocked(0, 0);
         } else {
@@ -606,9 +623,9 @@
             return;
         }
         if (mSetupWizardComplete) {
-            mNightMode = Secure.getIntForUser(context.getContentResolver(),
+            mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
                     Secure.UI_NIGHT_MODE, res.getInteger(
-                            com.android.internal.R.integer.config_defaultNightMode), userId);
+                            com.android.internal.R.integer.config_defaultNightMode), userId));
             mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
                     Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
                     mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
@@ -623,7 +640,7 @@
                     Secure.getLongForUser(context.getContentResolver(),
                             Secure.DARK_THEME_CUSTOM_END_TIME,
                             DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
-            if (mNightMode == MODE_NIGHT_AUTO) {
+            if (mNightMode.get() == MODE_NIGHT_AUTO) {
                 mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
                         Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
             }
@@ -834,21 +851,23 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    if (mNightMode != mode || mNightModeCustomType != customModeType) {
-                        if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+                    if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
+                        if (mNightMode.get() == MODE_NIGHT_AUTO
+                                || mNightMode.get() == MODE_NIGHT_CUSTOM) {
                             unregisterDeviceInactiveListenerLocked();
                             cancelCustomAlarm();
                         }
                         mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
                                 ? customModeType
                                 : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
-                        mNightMode = mode;
+                        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
-                        if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+                        if ((mNightMode.get() != MODE_NIGHT_AUTO
+                                && mNightMode.get() != MODE_NIGHT_CUSTOM)
                                 || shouldApplyAutomaticChangesImmediately()) {
                             unregisterDeviceInactiveListenerLocked();
                             updateLocked(0, 0);
@@ -865,7 +884,7 @@
         @Override
         public int getNightMode() {
             synchronized (mLock) {
-                return mNightMode;
+                return mNightMode.get();
             }
         }
 
@@ -999,18 +1018,19 @@
             synchronized (mLock) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+                    if (mNightMode.get() == MODE_NIGHT_AUTO
+                            || mNightMode.get() == MODE_NIGHT_CUSTOM) {
                         unregisterDeviceInactiveListenerLocked();
                         mOverrideNightModeOff = !active;
                         mOverrideNightModeOn = active;
                         mOverrideNightModeUser = mCurrentUser;
                         persistNightModeOverrides(mCurrentUser);
-                    } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
                             && active) {
-                        mNightMode = UiModeManager.MODE_NIGHT_YES;
-                    } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+                        mNightMode.set(UiModeManager.MODE_NIGHT_YES);
+                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
                             && !active) {
-                        mNightMode = UiModeManager.MODE_NIGHT_NO;
+                        mNightMode.set(UiModeManager.MODE_NIGHT_NO);
                     }
                     updateConfigurationLocked();
                     applyConfigurationExternallyLocked();
@@ -1414,7 +1434,7 @@
 
     private void onCustomTimeUpdated(int user) {
         persistNightMode(user);
-        if (mNightMode != MODE_NIGHT_CUSTOM) return;
+        if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
         if (shouldApplyAutomaticChangesImmediately()) {
             unregisterDeviceInactiveListenerLocked();
             updateLocked(0, 0);
@@ -1431,8 +1451,8 @@
 
             pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
 
-            pw.print("  mNightMode="); pw.print(mNightMode); pw.print(" (");
-            pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
+            pw.print("  mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
+            pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
             pw.print("/"); pw.print(mOverrideNightModeOff);
             pw.print("  mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
@@ -1623,7 +1643,7 @@
         // Only persist setting if not in car mode
         if (mCarModeEnabled || mCar) return;
         Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.UI_NIGHT_MODE, mNightMode, user);
+                Secure.UI_NIGHT_MODE, mNightMode.get(), user);
         Secure.putLongForUser(getContext().getContentResolver(),
                 Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
         Secure.putLongForUser(getContext().getContentResolver(),
@@ -1659,11 +1679,11 @@
             uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
         }
 
-        if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
-            updateComputedNightModeLocked(mNightMode == MODE_NIGHT_YES);
+        if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
+            updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
         }
 
-        if (mNightMode == MODE_NIGHT_AUTO) {
+        if (mNightMode.get() == MODE_NIGHT_AUTO) {
             boolean activateNightMode = mComputedNightMode;
             if (mTwilightManager != null) {
                 mTwilightManager.registerListener(mTwilightListener, mHandler);
@@ -1677,7 +1697,7 @@
             }
         }
 
-        if (mNightMode == MODE_NIGHT_CUSTOM) {
+        if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
             if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
                 updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
             } else {
@@ -2010,7 +2030,7 @@
 
     private void updateComputedNightModeLocked(boolean activate) {
         boolean newComputedValue = activate;
-        if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+        if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
             if (mOverrideNightModeOn && !newComputedValue) {
                 newComputedValue = true;
             } else if (mOverrideNightModeOff && newComputedValue) {
@@ -2029,7 +2049,7 @@
             mComputedNightMode = newComputedValue;
         }
 
-        if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
+        if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
                 && mTwilightManager.getLastTwilightState() != null)) {
             resetNightModeOverrideLocked();
         }
@@ -2279,4 +2299,13 @@
             Sandman.startDreamWhenDockedIfAppropriate(context);
         }
     }
+
+    /**
+     * Interface to contain the value for system night mode. We make the night mode accessible
+     * through this class to ensure that the reassignment of this value invalidates the cache.
+     */
+    private interface NightMode {
+        int get();
+        void set(int mode);
+    }
 }
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98129ed..856a15f 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -222,7 +222,7 @@
         // Ensure that the current activity supports assist data
         boolean isAssistDataAllowed = false;
         try {
-            isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowedOnCurrentActivity();
+            isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowed();
         } catch (RemoteException e) {
             // Should never happen
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index c37e619..d1c8c30 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -152,6 +152,11 @@
     private int mActiveIndex;
 
     /**
+     * True if the broadcast actively being dispatched to this process was re-enqueued previously.
+     */
+    private boolean mActiveReEnqueued;
+
+    /**
      * Count of {@link #mActive} broadcasts that have been dispatched since this
      * queue was last idle.
      */
@@ -312,6 +317,7 @@
         final SomeArgs broadcastArgs = SomeArgs.obtain();
         broadcastArgs.arg1 = record;
         broadcastArgs.argi1 = recordIndex;
+        broadcastArgs.argi2 = 1;
         getQueueForBroadcast(record).addFirst(broadcastArgs);
         onBroadcastEnqueued(record, recordIndex);
     }
@@ -609,6 +615,7 @@
         final SomeArgs next = removeNextBroadcast();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
+        mActiveReEnqueued = (next.argi2 == 1);
         mActiveCountSinceIdle++;
         mActiveAssumedDeliveryCountSinceIdle +=
                 (mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0);
@@ -624,12 +631,21 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
+        mActiveReEnqueued = false;
         mActiveCountSinceIdle = 0;
         mActiveAssumedDeliveryCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
     }
 
+    public boolean wasActiveBroadcastReEnqueued() {
+        // If the flag is not enabled, treat as if the broadcast was never re-enqueued.
+        if (!Flags.avoidRepeatedBcastReEnqueues()) {
+            return false;
+        }
+        return mActiveReEnqueued;
+    }
+
     /**
      * Update summary statistics when the given record has been enqueued.
      */
@@ -1476,6 +1492,9 @@
         if (runningOomAdjusted) {
             pw.print("runningOomAdjusted:"); pw.println(runningOomAdjusted);
         }
+        if (mActiveReEnqueued) {
+            pw.print("activeReEnqueued:"); pw.println(mActiveReEnqueued);
+        }
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index db0f03f..98263df 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -542,8 +542,8 @@
                 updateOomAdj |= queue.runningOomAdjusted;
                 try {
                     completed = scheduleReceiverWarmLocked(queue);
-                } catch (BroadcastDeliveryFailedException e) {
-                    reEnqueueActiveBroadcast(queue);
+                } catch (BroadcastRetryException e) {
+                    finishOrReEnqueueActiveBroadcast(queue);
                     completed = true;
                 }
             } else {
@@ -586,7 +586,12 @@
 
     private void clearInvalidPendingColdStart() {
         logw("Clearing invalid pending cold start: " + mRunningColdStart);
-        mRunningColdStart.reEnqueueActiveBroadcast();
+        if (mRunningColdStart.wasActiveBroadcastReEnqueued()) {
+            finishReceiverActiveLocked(mRunningColdStart, BroadcastRecord.DELIVERY_FAILURE,
+                    "invalid start with re-enqueued broadcast");
+        } else {
+            mRunningColdStart.reEnqueueActiveBroadcast();
+        }
         demoteFromRunningLocked(mRunningColdStart);
         clearRunningColdStart();
         enqueueUpdateRunningList();
@@ -613,19 +618,26 @@
         }
     }
 
-    private void reEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
+    private void finishOrReEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
-        final BroadcastRecord record = queue.getActive();
-        final int index = queue.getActiveIndex();
-        setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
-                BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
-        queue.reEnqueueActiveBroadcast();
+        if (queue.wasActiveBroadcastReEnqueued()) {
+            // If the broadcast was already re-enqueued previously, finish it to avoid repeated
+            // delivery attempts
+            finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "re-enqueued broadcast delivery failed");
+        } else {
+            final BroadcastRecord record = queue.getActive();
+            final int index = queue.getActiveIndex();
+            setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
+                    BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
+            queue.reEnqueueActiveBroadcast();
+        }
     }
 
     @Override
     public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app)
-            throws BroadcastDeliveryFailedException {
+            throws BroadcastRetryException {
         if (DEBUG_BROADCAST) {
             logv("Process " + app + " is attached");
         }
@@ -653,8 +665,8 @@
                 if (scheduleReceiverWarmLocked(queue)) {
                     demoteFromRunningLocked(queue);
                 }
-            } catch (BroadcastDeliveryFailedException e) {
-                reEnqueueActiveBroadcast(queue);
+            } catch (BroadcastRetryException e) {
+                finishOrReEnqueueActiveBroadcast(queue);
                 demoteFromRunningLocked(queue);
                 throw e;
             }
@@ -983,7 +995,7 @@
     @CheckResult
     @GuardedBy("mService")
     private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue)
-            throws BroadcastDeliveryFailedException {
+            throws BroadcastRetryException {
         checkState(queue.isActive(), "isActive");
 
         final int cookie = traceBegin("scheduleReceiverWarmLocked");
@@ -1065,7 +1077,7 @@
      */
     @CheckResult
     private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue,
-            @NonNull BroadcastRecord r, int index) throws BroadcastDeliveryFailedException {
+            @NonNull BroadcastRecord r, int index) throws BroadcastRetryException {
         final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
@@ -1157,7 +1169,7 @@
                 // to try redelivering the broadcast to this receiver.
                 if (receiver instanceof ResolveInfo) {
                     cancelDeliveryTimeoutLocked(queue);
-                    throw new BroadcastDeliveryFailedException(e);
+                    throw new BroadcastRetryException(e);
                 }
                 finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
                         "remote app");
@@ -1316,8 +1328,8 @@
                 demoteFromRunningLocked(queue);
                 return true;
             }
-        } catch (BroadcastDeliveryFailedException e) {
-            reEnqueueActiveBroadcast(queue);
+        } catch (BroadcastRetryException e) {
+            finishOrReEnqueueActiveBroadcast(queue);
             demoteFromRunningLocked(queue);
             return true;
         }
diff --git a/services/core/java/com/android/server/am/BroadcastRetryException.java b/services/core/java/com/android/server/am/BroadcastRetryException.java
new file mode 100644
index 0000000..8bd6664
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastRetryException.java
@@ -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.server.am;
+
+/**
+ * Exception to represent that broadcast delivery failed and we should try redelivering it.
+ */
+public class BroadcastRetryException extends BroadcastDeliveryFailedException {
+    public BroadcastRetryException(String name) {
+        super(name);
+    }
+
+    public BroadcastRetryException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 2aed847..0f75ad48 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -31,7 +31,7 @@
 30017 am_low_memory (Num Processes|1|1)
 
 # Kill a process to reclaim memory.
-30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3),(Rss|2|2)
 # Discard an undelivered serialized broadcast (timeout/ANR/crash)
 30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
 30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 862542e..7d82f0c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1499,8 +1499,12 @@
                             && !uidRec.isCurAllowListed()) {
                         // UID is now in the background (and not on the temp allowlist).  Was it
                         // previously in the foreground (or on the temp allowlist)?
+                        // Or, it wasn't in the foreground / allowlist, but its last background
+                        // timestamp is also 0, this means it's never been in the
+                        // foreground / allowlist since it's born at all.
                         if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
-                                || uidRec.isSetAllowListed()) {
+                                || uidRec.isSetAllowListed()
+                                || uidRec.getLastBackgroundTime() == 0) {
                             uidRec.setLastBackgroundTime(nowElapsed);
                             if (mService.mDeterministicUidIdle
                                     || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
@@ -1526,6 +1530,7 @@
                             uidRec.setIdle(false);
                         }
                         uidRec.setLastBackgroundTime(0);
+                        uidRec.setLastIdleTime(0);
                     }
                     final boolean wasCached = uidRec.getSetProcState()
                             > ActivityManager.PROCESS_STATE_RECEIVER;
@@ -3700,12 +3705,14 @@
         for (int i = N - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
             final long bgTime = uidRec.getLastBackgroundTime();
-            if (bgTime > 0 && !uidRec.isIdle()) {
+            final long idleTime = uidRec.getLastIdleTime();
+            if (bgTime > 0 && (!uidRec.isIdle() || idleTime == 0)) {
                 if (bgTime <= maxBgTime) {
                     EventLogTags.writeAmUidIdle(uidRec.getUid());
                     synchronized (mProcLock) {
                         uidRec.setIdle(true);
                         uidRec.setSetIdle(true);
+                        uidRec.setLastIdleTime(nowElapsed);
                     }
                     mService.doStopUidLocked(uidRec.getUid(), uidRec);
                 } else {
diff --git a/services/core/java/com/android/server/am/PhantomProcessRecord.java b/services/core/java/com/android/server/am/PhantomProcessRecord.java
index 1a692df..ac96bdc 100644
--- a/services/core/java/com/android/server/am/PhantomProcessRecord.java
+++ b/services/core/java/com/android/server/am/PhantomProcessRecord.java
@@ -105,6 +105,11 @@
         }
     }
 
+    public long getRss(int pid) {
+        long[] rss = Process.getRss(pid);
+        return (rss != null && rss.length > 0) ? rss[0] : 0;
+    }
+
     @GuardedBy("mLock")
     void killLocked(String reason, boolean noisy) {
         if (!mKilled) {
@@ -115,7 +120,7 @@
             }
             if (mPid > 0) {
                 EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid),
-                        mPid, mProcessName, mAdj, reason);
+                        mPid, mProcessName, mAdj, reason, getRss(mPid));
                 if (!Process.supportsPidFd()) {
                     onProcDied(false);
                 } else {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 10cd6e5..3adea7a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1950,7 +1950,8 @@
                 mService.mNativeDebuggingApp = null;
             }
 
-            if (app.info.isEmbeddedDexUsed()) {
+            if (app.info.isEmbeddedDexUsed()
+                    || (app.processInfo != null && app.processInfo.useEmbeddedDex)) {
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
             }
 
@@ -2498,7 +2499,7 @@
                         app.info.dataDir, null, app.info.packageName,
                         /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
                         app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
-                        false, false, bindOverrideSysprops,
+                        false, false, false,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else {
                 regularZygote = true;
@@ -4985,19 +4986,7 @@
     }
 
     void dispatchProcessStarted(ProcessRecord app, int pid) {
-        int i = mProcessObservers.beginBroadcast();
-        while (i > 0) {
-            i--;
-            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
-            if (observer != null) {
-                try {
-                    observer.onProcessStarted(pid, app.uid, app.info.uid,
-                            app.info.packageName, app.processName);
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        mProcessObservers.finishBroadcast();
+        // TODO(b/323959187) Add the implementation.
     }
 
     void dispatchProcessDied(int pid, int uid) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index de6f034..d23d9fb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1217,6 +1217,11 @@
         }
     }
 
+    public long getRss(int pid) {
+        long[] rss = Process.getRss(pid);
+        return (rss != null && rss.length > 0) ? rss[0] : 0;
+    }
+
     @GuardedBy("mService")
     void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
         killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
@@ -1260,7 +1265,7 @@
             if (mPid > 0) {
                 mService.mProcessList.noteAppKill(this, reasonCode, subReason, description);
                 EventLog.writeEvent(EventLogTags.AM_KILL,
-                        userId, mPid, processName, mState.getSetAdj(), reason);
+                        userId, mPid, processName, mState.getSetAdj(), reason, getRss(mPid));
                 Process.killProcessQuiet(mPid);
                 killProcessGroupIfNecessaryLocked(asyncKPG);
             } else {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index e90910a..bd3c8e0 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -155,6 +155,16 @@
         { "exclude-annotation": "androidx.test.filters.FlakyTest" },
         { "exclude-annotation": "org.junit.Ignore" }
       ]
+    },
+    {
+      "file_patterns": ["Broadcast.*"],
+      "name": "CtsContentTestCases",
+      "options": [
+        { "include-filter": "android.content.cts.BroadcastReceiverTest" },
+        { "exclude-annotation": "androidx.test.filters.LargeTest" },
+        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+        { "exclude-annotation": "org.junit.Ignore" }
+      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 4329afc..45fd470 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -66,6 +66,9 @@
     private long mLastBackgroundTime;
 
     @CompositeRWLock({"mService", "mProcLock"})
+    private long mLastIdleTime;
+
+    @CompositeRWLock({"mService", "mProcLock"})
     private boolean mEphemeral;
 
     @CompositeRWLock({"mService", "mProcLock"})
@@ -255,6 +258,16 @@
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getLastIdleTime() {
+        return mLastIdleTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setLastIdleTime(long lastActiveTime) {
+        mLastIdleTime = lastActiveTime;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     boolean isEphemeral() {
         return mEphemeral;
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2b81dbc..96c6be8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -993,17 +993,20 @@
     /**
      * Stops a single User. This can also trigger locking user data out depending on device's
      * config ({@code mDelayUserDataLocking}) and arguments.
-     * User will be unlocked when
-     * - {@code mDelayUserDataLocking} is not set.
-     * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+     *
+     * In the default configuration for most device and users, users will be locked when stopping.
+     * User will remain unlocked only if all the following are true
+     * <li> {@link #canDelayDataLockingForUser(int)} (based on mDelayUserDataLocking) is true
+     * <li> the parameter {@code allowDelayedLocking} is true
+     * <li> {@code keyEvictedCallback} is null
      * -
      *
      * @param userId User Id to stop and lock the data.
      * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
      *                            later when number of unlocked users reaches
      *                            {@code mMaxRunnngUsers}. Note that this is respected only when
-     *                            {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
-     *                            null. Otherwise the user will be locked.
+     *                            delayed locking is enabled for this user and {@keyEvictedCallback}
+     *                            is null. Otherwise the user nonetheless will be locked.
      * @param stopUserCallback Callback to notify that user has stopped.
      * @param keyEvictedCallback Callback to notify that user has been unlocked.
      */
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 31d9cc9..16dbe18 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -42,3 +42,14 @@
     description: "Optimize the service bindings by different policies like skipping oom adjuster"
     bug: "318717054"
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "avoid_repeated_bcast_re_enqueues"
+    description: "Avoid re-enqueueing a broadcast repeatedly"
+    bug: "319225224"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cd295b5..f1eea72 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2820,6 +2820,10 @@
         return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
     }
 
+    /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+        return mAudioService.isSADevice(deviceState);
+    }
+
     //------------------------------------------------
     // for testing purposes only
     void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 690c37a..102a960 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -307,9 +307,12 @@
                             && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
                         continue;
                     }
-                    ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                    ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                    ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    if (mDeviceBroker.isSADevice(updatedDevice)
+                            == mDeviceBroker.isSADevice(ads2)) {
+                        ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                        ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                        ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    }
                     ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
                     mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -325,9 +328,12 @@
                             && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
                         continue;
                     }
-                    ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                    ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                    ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    if (mDeviceBroker.isSADevice(updatedDevice)
+                            == mDeviceBroker.isSADevice(ads2)) {
+                        ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                        ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                        ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    }
                     ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
                     mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -348,10 +354,11 @@
                     || !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
                 continue;
             }
-
-            ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
-            ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-            ads.setSAEnabled(updatedDevice.isSAEnabled());
+            if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
+                ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                ads.setSAEnabled(updatedDevice.isSAEnabled());
+            }
             ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
             mDeviceBroker.postUpdatedAdiDeviceState(ads);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9f7c07e..bbbba26 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,7 +7855,7 @@
 
         sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
                 + BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
-                + " -> " + newDevice));
+                + " -> " + newDevice).printLog(TAG));
         AudioDeviceBroker.BtDeviceChangedData data =
                 new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
                         "AudioService");
@@ -10638,6 +10638,10 @@
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
+    /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+        return mSpatializerHelper.isSADevice(deviceState);
+    }
+
     private boolean isBluetoothPrividged() {
         if (!bluetoothMacAddressAnonymization()) {
             return true;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8428f12..8d76731 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -813,7 +813,7 @@
         return false;
     }
 
-    private boolean isSADevice(AdiDeviceState deviceState) {
+    /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
         return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
                 deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
                 deviceState.getAudioDeviceAttributes());
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
new file mode 100644
index 0000000..a923daa
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.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 com.android.server.biometrics;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * This class provides the handler to process biometric operations.
+ */
+public class BiometricHandlerProvider {
+    private static final BiometricHandlerProvider sBiometricHandlerProvider =
+            new BiometricHandlerProvider();
+
+    private final Handler mBiometricsCallbackHandler;
+    private final Handler mFingerprintHandler;
+    private final Handler mFaceHandler;
+
+    /**
+     * @return an instance of {@link BiometricHandlerProvider} which contains the three
+     *         threads needed for running biometric operations
+     */
+    public static BiometricHandlerProvider getInstance() {
+        return sBiometricHandlerProvider;
+    }
+
+    private BiometricHandlerProvider() {
+        mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
+        mFingerprintHandler = getNewHandler("FingerprintHandler");
+        mFaceHandler = getNewHandler("FaceHandler");
+    }
+
+    /**
+    * @return the handler to process all biometric callback operations
+    */
+    public synchronized Handler getBiometricCallbackHandler() {
+        return mBiometricsCallbackHandler;
+    }
+
+    /**
+     * @return the handler to process all face related biometric operations
+     */
+    public synchronized Handler getFaceHandler() {
+        return mFaceHandler;
+    }
+
+    /**
+     * @return the handler to process all fingerprint related biometric operations
+     */
+    public synchronized Handler getFingerprintHandler() {
+        return mFingerprintHandler;
+    }
+
+    private Handler getNewHandler(String tag) {
+        if (Flags.deHidl()) {
+            HandlerThread handlerThread = new HandlerThread(tag);
+            handlerThread.start();
+            return new Handler(handlerThread.getLooper());
+        }
+        return new Handler(Looper.getMainLooper());
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 91a68ea..fc948da 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -62,7 +62,6 @@
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
@@ -140,7 +139,7 @@
     // The current authentication session, null if idle/done.
     @VisibleForTesting
     AuthSession mAuthSession;
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Handler mHandler;
 
     private final BiometricCameraManager mBiometricCameraManager;
 
@@ -1113,14 +1112,16 @@
      * @param context The system server context.
      */
     public BiometricService(Context context) {
-        this(context, new Injector());
+        this(context, new Injector(), BiometricHandlerProvider.getInstance());
     }
 
     @VisibleForTesting
-    BiometricService(Context context, Injector injector) {
+    BiometricService(Context context, Injector injector,
+            BiometricHandlerProvider biometricHandlerProvider) {
         super(context);
 
         mInjector = injector;
+        mHandler = biometricHandlerProvider.getBiometricCallbackHandler();
         mDevicePolicyManager = mInjector.getDevicePolicyManager(context);
         mImpl = new BiometricServiceWrapper();
         mEnabledOnKeyguardCallbacks = new ArrayList<>();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 1ae4d64..1dc882e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.hardware.biometrics.AuthenticationStateListener;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -115,7 +117,7 @@
      * @param userId The user Id for the requested authentication
      */
     public void onAuthenticationFailed(int requestReason, int userId) {
-        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+        for (AuthenticationStateListener listener : mAuthenticationStateListeners) {
             try {
                 listener.onAuthenticationFailed(requestReason, userId);
             } catch (RemoteException e) {
@@ -125,6 +127,27 @@
         }
     }
 
+    /**
+     * Defines behavior in response to biometric being acquired.
+     * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+     * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+     * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+     *                     a known acquired message.
+     */
+    public void onAuthenticationAcquired(
+            BiometricSourceType biometricSourceType, int requestReason,
+            @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo
+    ) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationAcquired(biometricSourceType, requestReason, acquiredInfo);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "stopped", e);
+            }
+        }
+    }
+
     @Override
     public void binderDied() {
         // Do nothing, handled below
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 d01c268..f469f62 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
@@ -39,7 +39,6 @@
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -53,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -124,6 +124,8 @@
     private final BiometricContext mBiometricContext;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final BiometricHandlerProvider mBiometricHandlerProvider;
     @Nullable
     private AuthenticationStatsCollector mAuthenticationStatsCollector;
     @Nullable
@@ -166,8 +168,9 @@
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
-                lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
-                resetLockoutRequiresChallenge, false /* testHalEnabled */);
+                lockoutResetDispatcher, biometricContext, null /* daemon */,
+                BiometricHandlerProvider.getInstance(), resetLockoutRequiresChallenge,
+                false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
@@ -178,7 +181,7 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             @Nullable IFace daemon,
-            @NonNull Handler handler,
+            @NonNull BiometricHandlerProvider biometricHandlerProvider,
             boolean resetLockoutRequiresChallenge,
             boolean testHalEnabled) {
         mContext = context;
@@ -187,7 +190,7 @@
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
         if (Flags.deHidl()) {
-            mHandler = handler;
+            mHandler = biometricHandlerProvider.getFaceHandler();
         } else {
             mHandler = new Handler(Looper.getMainLooper());
         }
@@ -199,18 +202,12 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
         mTestHalEnabled = testHalEnabled;
+        mBiometricHandlerProvider = biometricHandlerProvider;
 
         initAuthenticationBroadcastReceiver();
         initSensors(resetLockoutRequiresChallenge, props);
     }
 
-    @NonNull
-    private static Handler getHandler() {
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        return new Handler(handlerThread.getLooper());
-    }
-
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -622,15 +619,29 @@
                 @Override
                 public void onClientStarted(
                          BaseClientMonitor clientMonitor) {
-                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+                                        requestId));
+                    } else {
+                        mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    }
                 }
 
                 @Override
                 public void onClientFinished(
                         BaseClientMonitor clientMonitor,
                         boolean success) {
-                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, client.wasAuthSuccessful());
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authEndedFor(userId,
+                                        Utils.getCurrentStrength(sensorId), sensorId, requestId,
+                                        client.wasAuthSuccessful()));
+                    } else {
+                        mAuthSessionCoordinator.authEndedFor(userId,
+                                Utils.getCurrentStrength(sensorId),
+                                sensorId, requestId, client.wasAuthSuccessful());
+                    }
                 }
             });
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index e0fd44b..8121a63 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
@@ -102,6 +103,7 @@
     private Runnable mAuthSuccessRunnable;
     private final Clock mClock;
 
+
     public FingerprintAuthenticationClient(
             @NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
@@ -280,6 +282,8 @@
     public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
         // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
         // for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired
+        mAuthenticationStateListeners.onAuthenticationAcquired(
+                BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
         mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
         super.onAcquired(acquiredInfo, vendorCode);
         PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c0388d1..fd938ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -46,7 +46,6 @@
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -60,6 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -129,11 +129,12 @@
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final BiometricHandlerProvider mBiometricHandlerProvider;
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Nullable private ISidefpsController mSidefpsController;
-    private AuthSessionCoordinator mAuthSessionCoordinator;
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -175,8 +176,8 @@
             boolean resetLockoutRequiresHardwareAuthToken) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
                 lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
-                null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
-                false /* testHalEnabled */);
+                null /* daemon */, BiometricHandlerProvider.getInstance(),
+                resetLockoutRequiresHardwareAuthToken, false /* testHalEnabled */);
     }
 
     @VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -187,7 +188,7 @@
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             @Nullable IFingerprint daemon,
-            @NonNull Handler handler,
+            @NonNull BiometricHandlerProvider biometricHandlerProvider,
             boolean resetLockoutRequiresHardwareAuthToken,
             boolean testHalEnabled) {
         mContext = context;
@@ -196,7 +197,7 @@
         mHalInstanceName = halInstanceName;
         mFingerprintSensors = new SensorList<>(ActivityManager.getService());
         if (Flags.deHidl()) {
-            mHandler = handler;
+            mHandler = biometricHandlerProvider.getFingerprintHandler();
         } else {
             mHandler = new Handler(Looper.getMainLooper());
         }
@@ -207,18 +208,12 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
         mTestHalEnabled = testHalEnabled;
+        mBiometricHandlerProvider = biometricHandlerProvider;
 
         initAuthenticationBroadcastReceiver();
         initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
     }
 
-    @NonNull
-    private static Handler getHandler() {
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        return new Handler(handlerThread.getLooper());
-    }
-
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -620,7 +615,13 @@
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                     mBiometricStateCallback.onClientStarted(clientMonitor);
-                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+                                        requestId));
+                    } else {
+                        mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    }
                 }
 
                 @Override
@@ -632,8 +633,15 @@
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
                     mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, success);
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authEndedFor(userId,
+                                        Utils.getCurrentStrength(sensorId), sensorId, requestId,
+                                        success));
+                    } else {
+                        mAuthSessionCoordinator.authEndedFor(userId,
+                                Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
+                    }
                 }
             });
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 60c532c..b6311af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -201,6 +202,8 @@
 
     @Override
     public void onAcquired(int acquiredInfo, int vendorCode) {
+        mAuthenticationStateListeners.onAuthenticationAcquired(
+                BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
         super.onAcquired(acquiredInfo, vendorCode);
 
         @LockoutTracker.LockoutMode final int lockoutMode =
diff --git a/services/core/java/com/android/server/broadcastradio/OWNERS b/services/core/java/com/android/server/broadcastradio/OWNERS
index d2bdd64..51a85e4 100644
--- a/services/core/java/com/android/server/broadcastradio/OWNERS
+++ b/services/core/java/com/android/server/broadcastradio/OWNERS
@@ -1,3 +1,3 @@
 xuweilin@google.com
 oscarazu@google.com
-keunyoung@google.com
+ericjeong@google.com
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 77cb08b..cb15abc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -914,6 +914,9 @@
             }
 
             mOverrideRequestController.dumpInternal(pw);
+            pw.println();
+
+            mDeviceStatePolicy.dump(pw, /* args= */ null);
         }
     }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
index 5c4e2f3..65e5085 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.text.TextUtils;
+import android.util.Dumpable;
 
 import com.android.server.policy.DeviceStatePolicyImpl;
 
@@ -29,7 +30,7 @@
  *
  * @see DeviceStateManagerService
  */
-public abstract class DeviceStatePolicy {
+public abstract class DeviceStatePolicy implements Dumpable {
     protected final Context mContext;
 
     protected DeviceStatePolicy(@NonNull Context context) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 50ab3f8..d5945f4 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.util.Dumpable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -31,7 +32,7 @@
  *
  * @see DeviceStatePolicy
  */
-public interface DeviceStateProvider {
+public interface DeviceStateProvider extends Dumpable {
     int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 93addcd..7ebc311 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -695,14 +695,14 @@
                             logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
                             userSerial);
                     dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
-                    // change the brightness value according to the selected user.
-                    final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
-                    if (device != null) {
-                        dpc.setBrightness(
-                                mPersistentDataStore.getBrightness(device, userSerial), userSerial);
-                    }
                 }
-                dpc.onSwitchUser(newUserId);
+                final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+                float newBrightness = device == null ? PowerManager.BRIGHTNESS_INVALID_FLOAT
+                        : mPersistentDataStore.getBrightness(device, userSerial);
+                if (Float.isNaN(newBrightness)) {
+                    newBrightness = logicalDisplay.getDisplayInfoLocked().brightnessDefault;
+                }
+                dpc.onSwitchUser(newUserId, userSerial, newBrightness);
             });
             handleSettingsChange();
         }
@@ -872,14 +872,15 @@
     }
 
     @VisibleForTesting
-    void performTraversalInternal(SurfaceControl.Transaction t) {
+    void performTraversalInternal(SurfaceControl.Transaction t,
+            SparseArray<SurfaceControl.Transaction> displayTransactions) {
         synchronized (mSyncRoot) {
             if (!mPendingTraversal) {
                 return;
             }
             mPendingTraversal = false;
 
-            performTraversalLocked(t);
+            performTraversalLocked(t, displayTransactions);
         }
 
         // List is self-synchronized copy-on-write.
@@ -2593,7 +2594,8 @@
         }
     }
 
-    private void performTraversalLocked(SurfaceControl.Transaction t) {
+    private void performTraversalLocked(SurfaceControl.Transaction t,
+            SparseArray<SurfaceControl.Transaction> displayTransactions) {
         // Clear all viewports before configuring displays so that we can keep
         // track of which ones we have configured.
         clearViewportsLocked();
@@ -2601,9 +2603,11 @@
         // Configure each display device.
         mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
             final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+            final SurfaceControl.Transaction displayTransaction =
+                    displayTransactions.get(display.getDisplayIdLocked(), t);
             if (device != null) {
-                configureDisplayLocked(t, device);
-                device.performTraversalLocked(t);
+                configureDisplayLocked(displayTransaction, device);
+                device.performTraversalLocked(displayTransaction);
             }
         });
 
@@ -4680,8 +4684,9 @@
         }
 
         @Override
-        public void performTraversal(SurfaceControl.Transaction t) {
-            performTraversalInternal(t);
+        public void performTraversal(SurfaceControl.Transaction t,
+                SparseArray<SurfaceControl.Transaction> displayTransactions) {
+            performTraversalInternal(t, displayTransactions);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 087cacf..1ca3923 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -685,17 +685,27 @@
     }
 
     @Override
-    public void onSwitchUser(@UserIdInt int newUserId) {
-        Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
-        mHandler.sendMessage(msg);
+    public void onSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+        Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId, userSerial, newBrightness);
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
-    private void handleOnSwitchUser(@UserIdInt int newUserId) {
-        handleSettingsChange(true /* userSwitch */);
+    private void handleOnSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+        Slog.i(mTag, "Switching user newUserId=" + newUserId + " userSerial=" + userSerial
+                + " newBrightness=" + newBrightness);
         handleBrightnessModeChange();
         if (mBrightnessTracker != null) {
             mBrightnessTracker.onSwitchUser(newUserId);
         }
+        setBrightness(newBrightness, userSerial);
+
+        // Don't treat user switches as user initiated change.
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(newBrightness);
+
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.resetShortTermModel();
+        }
+        sendUpdatePowerState();
     }
 
     @Nullable
@@ -2394,20 +2404,11 @@
         MetricsLogger.action(log);
     }
 
-    private void handleSettingsChange(boolean userSwitch) {
+    private void handleSettingsChange() {
         mDisplayBrightnessController
                 .setPendingScreenBrightness(mDisplayBrightnessController
                         .getScreenBrightnessSetting());
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
-        if (userSwitch) {
-            // Don't treat user switches as user initiated change.
-            mDisplayBrightnessController
-                    .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
-                            .getPendingScreenBrightness());
-            if (mAutomaticBrightnessController != null) {
-                mAutomaticBrightnessController.resetShortTermModel();
-            }
-        }
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         sendUpdatePowerState();
     }
 
@@ -2416,11 +2417,8 @@
                 mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-        mHandler.postAtTime(() -> {
-            mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
-                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-            updatePowerState();
-        }, mClock.uptimeMillis());
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+                == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
     }
 
 
@@ -2430,9 +2428,13 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue, int userSerial) {
-        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
-                userSerial);
+    public void setBrightness(float brightness) {
+        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
+    }
+
+    @Override
+    public void setBrightness(float brightness, int userSerial) {
+        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial);
     }
 
     @Override
@@ -2966,7 +2968,7 @@
                     if (mStopped) {
                         return;
                     }
-                    handleSettingsChange(false /*userSwitch*/);
+                    handleSettingsChange();
                     break;
 
                 case MSG_UPDATE_RBC:
@@ -2985,7 +2987,9 @@
                     break;
 
                 case MSG_SWITCH_USER:
-                    handleOnSwitchUser(msg.arg1);
+                    float newBrightness = msg.obj instanceof Float ? (float) msg.obj
+                            : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                    handleOnSwitchUser(msg.arg1, msg.arg2, newBrightness);
                     break;
 
                 case MSG_BOOT_COMPLETED:
@@ -3023,7 +3027,10 @@
         @Override
         public void onChange(boolean selfChange, Uri uri) {
             if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
-                handleBrightnessModeChange();
+                mHandler.postAtTime(() -> {
+                    handleBrightnessModeChange();
+                    updatePowerState();
+                }, mClock.uptimeMillis());
             } else if (uri.equals(Settings.System.getUriFor(
                     Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
                 int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -3035,7 +3042,7 @@
                 setUpAutoBrightness(mContext, mHandler);
                 sendUpdatePowerState();
             } else {
-                handleSettingsChange(false /* userSwitch */);
+                handleSettingsChange();
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 13acb3f..ecf1635 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -30,7 +30,6 @@
  * An interface to manage the display's power state and brightness
  */
 public interface DisplayPowerControllerInterface {
-    int DEFAULT_USER_SERIAL = -1;
     /**
      * Notified when the display is changed.
      *
@@ -100,15 +99,12 @@
      * Set the screen brightness of the associated display
      * @param brightness The value to which the brightness is to be set
      */
-    default void setBrightness(float brightness) {
-        setBrightness(brightness, DEFAULT_USER_SERIAL);
-    }
+    void setBrightness(float brightness);
 
     /**
      * Set the screen brightness of the associated display
      * @param brightness The value to which the brightness is to be set
-     * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
-     * if brightness needs to be updated for the current user.
+     * @param userSerial The user for which the brightness value is to be set.
      */
     void setBrightness(float brightness, int userSerial);
 
@@ -188,8 +184,10 @@
     /**
      * Handles the changes to be done to update the brightness when the user is changed
      * @param newUserId The new userId
+     * @param userSerial The serial number of the new user
+     * @param newBrightness The brightness for the new user
      */
-    void onSwitchUser(int newUserId);
+    void onSwitchUser(int newUserId, int userSerial, float newBrightness);
 
     /**
      * Get the ID of the display associated with this DPC.
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 3bb7986..f6d02db 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -41,7 +41,6 @@
  * display. Applies the chosen brightness.
  */
 public final class DisplayBrightnessController {
-    private static final int DEFAULT_USER_SERIAL = -1;
 
     // The ID of the display tied to this DisplayBrightnessController
     private final int mDisplayId;
@@ -302,16 +301,8 @@
      * Notifies the brightnessSetting to persist the supplied brightness value.
      */
     public void setBrightness(float brightnessValue) {
-        setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
-    }
-
-    /**
-     * Notifies the brightnessSetting to persist the supplied brightness value for a user.
-     */
-    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually
         // update the display with the new value.
-        mBrightnessSetting.setUserSerial(userSerial);
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
@@ -322,6 +313,14 @@
     }
 
     /**
+     * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+     */
+    public void setBrightness(float brightnessValue, int userSerial) {
+        mBrightnessSetting.setUserSerial(userSerial);
+        setBrightness(brightnessValue);
+    }
+
+    /**
      * Sets the current screen brightness, and notifies the BrightnessSetting about the change.
      */
     public void updateScreenBrightnessSetting(float brightnessValue) {
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 3c23b5c..3e6e09d 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -203,14 +203,11 @@
      * Sets the pending auto-brightness adjustments in the system settings. Executed
      * when there is a change in the brightness system setting, or when there is a user switch.
      */
-    public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+    public void updatePendingAutoBrightnessAdjustments() {
         final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
         mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
                 : BrightnessUtils.clampBrightnessAdjustment(adj);
-        if (userSwitch) {
-            processPendingAutoBrightnessAdjustments();
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
index 8278600..202c894 100644
--- a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -98,18 +98,21 @@
             localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus);
             mState = STATE_MONITOR_AUDIO_STATUS;
         } else if (mState == STATE_MONITOR_AUDIO_STATUS) {
-            // On TV panels, we notify AudioService even if neither volume nor mute state changed.
-            // This ensures that the user sees volume UI if they tried to adjust the AVR's volume,
-            // even if the new volume level is the same as the previous one.
-            boolean notifyAvbVolumeToShowUi = localDevice().getService().isTvDevice()
-                    && audioStatus.equals(mLastAudioStatus);
-
-            if (audioStatus.getVolume() != mLastAudioStatus.getVolume()
-                    || notifyAvbVolumeToShowUi) {
+            // Update volume in AudioService if it has changed since the last <Report Audio Status>
+            boolean updateVolume = audioStatus.getVolume() != mLastAudioStatus.getVolume();
+            if (updateVolume) {
                 localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume());
             }
 
-            if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+            // Update mute in AudioService if any of the following conditions are met:
+            // - The mute status changed
+            // - The volume changed - we need to make sure mute is set correctly afterwards, since
+            //   setting volume can affect mute status as well as a side effect.
+            // - We're a TV panel - we want to trigger volume UI on TV panels, so that the user
+            //   always gets visual feedback when they attempt to adjust the AVR's volume/mute.
+            if ((audioStatus.getMute() != mLastAudioStatus.getMute())
+                    || updateVolume
+                    || localDevice().getService().isTvDevice()) {
                 localDevice().getService().notifyAvbMuteChange(audioStatus.getMute());
             }
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a15cb10..a23c08a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -716,7 +716,10 @@
             // Programmed
             int programedInfo = params[offset] & 0x0F;
             if (isValidProgrammedInfo(programedInfo)) {
-                if (programedInfo == 0x09 || programedInfo == 0x0B) {
+                offset = offset + 1;
+                // Duration Available (2 bytes)
+                if ((programedInfo == 0x09 || programedInfo == 0x0B)
+                        && params.length - offset >= 2) {
                     durationAvailable = true;
                 } else {
                     return true;
@@ -726,16 +729,17 @@
             // Non programmed
             int nonProgramedErrorInfo = params[offset] & 0x0F;
             if (isValidNotProgrammedErrorInfo(nonProgramedErrorInfo)) {
-                if (nonProgramedErrorInfo == 0x0E) {
+                offset = offset + 1;
+                // Duration Available (2 bytes)
+                if (nonProgramedErrorInfo == 0x0E && params.length - offset >= 2) {
                     durationAvailable = true;
                 } else {
                     return true;
                 }
             }
         }
-        offset = offset + 1;
         // Duration Available (2 bytes)
-        if (durationAvailable && params.length - offset >= 2) {
+        if (durationAvailable) {
             return (isValidDurationHours(params[offset]) && isValidMinute(params[offset + 1]));
         }
         return false;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 687def0..7726609 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -170,9 +170,6 @@
 
     private InputMethodManagerInternal mInputMethodManagerInternal;
 
-    // Context cache used for loading pointer resources.
-    private Context mPointerIconDisplayContext;
-
     private final File mDoubleTouchGestureEnableFile;
 
     private WindowManagerCallbacks mWindowManagerCallbacks;
@@ -416,6 +413,8 @@
             new SparseArray<>();
     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
     boolean mUseLargePointerIcons = false;
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    final SparseArray<Context> mDisplayContexts = new SparseArray<>();
 
     final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
         @Override
@@ -427,6 +426,7 @@
         public void onDisplayRemoved(int displayId) {
             synchronized (mLoadedPointerIconsByDisplayAndType) {
                 mLoadedPointerIconsByDisplayAndType.remove(displayId);
+                mDisplayContexts.remove(displayId);
             }
         }
 
@@ -440,6 +440,7 @@
                     return;
                 }
                 iconsByType.clear();
+                mDisplayContexts.remove(displayId);
             }
             mNative.reloadPointerIcons();
         }
@@ -1323,11 +1324,6 @@
 
     /** Clean up input window handles of the given display. */
     public void onDisplayRemoved(int displayId) {
-        if (mPointerIconDisplayContext != null
-                && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
-            mPointerIconDisplayContext = null;
-        }
-
         updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset);
 
         // TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been
@@ -1392,6 +1388,11 @@
     }
 
     @Override // Binder call
+    public int getMousePointerSpeed() {
+        return mNative.getMousePointerSpeed();
+    }
+
+    @Override // Binder call
     public void tryPointerSpeed(int speed) {
         if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
                 "tryPointerSpeed()")) {
@@ -2374,6 +2375,7 @@
         synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
         synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
         synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
+        synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by pointer lock */}
         mBatteryController.monitor();
         mNative.monitor();
     }
@@ -2777,7 +2779,7 @@
             }
             PointerIcon icon = iconsByType.get(type);
             if (icon == null) {
-                icon = PointerIcon.getLoadedSystemIcon(getContextForPointerIcon(displayId), type,
+                icon = PointerIcon.getLoadedSystemIcon(getContextForDisplay(displayId), type,
                         mUseLargePointerIcons);
                 iconsByType.put(type, icon);
             }
@@ -2795,40 +2797,31 @@
         return sc.mNativeObject;
     }
 
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
     @NonNull
-    private Context getContextForPointerIcon(int displayId) {
-        if (mPointerIconDisplayContext != null
-                && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
-            return mPointerIconDisplayContext;
-        }
-
-        // Create and cache context for non-default display.
-        mPointerIconDisplayContext = getContextForDisplay(displayId);
-
-        // Fall back to default display if the requested displayId does not exist.
-        if (mPointerIconDisplayContext == null) {
-            mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY);
-        }
-        return mPointerIconDisplayContext;
-    }
-
-    @Nullable
     private Context getContextForDisplay(int displayId) {
         if (displayId == Display.INVALID_DISPLAY) {
-            return null;
+            // Fallback to using the default context.
+            return mContext;
         }
-        if (mContext.getDisplay().getDisplayId() == displayId) {
+        if (displayId == mContext.getDisplay().getDisplayId()) {
             return mContext;
         }
 
-        final DisplayManager displayManager = Objects.requireNonNull(
-                mContext.getSystemService(DisplayManager.class));
-        final Display display = displayManager.getDisplay(displayId);
-        if (display == null) {
-            return null;
-        }
+        Context displayContext = mDisplayContexts.get(displayId);
+        if (displayContext == null) {
+            final DisplayManager displayManager = Objects.requireNonNull(
+                    mContext.getSystemService(DisplayManager.class));
+            final Display display = displayManager.getDisplay(displayId);
+            if (display == null) {
+                // Fallback to using the default context.
+                return mContext;
+            }
 
-        return mContext.createDisplayContext(display);
+            displayContext = mContext.createDisplayContext(display);
+            mDisplayContexts.put(displayId, displayContext);
+        }
+        return displayContext;
     }
 
     // Native callback.
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index e5f3484..b16df0f 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -119,6 +119,8 @@
      */
     boolean transferTouch(IBinder destChannelToken, int displayId);
 
+    int getMousePointerSpeed();
+
     void setPointerSpeed(int speed);
 
     void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@@ -364,6 +366,9 @@
         public native boolean transferTouch(IBinder destChannelToken, int displayId);
 
         @Override
+        public native int getMousePointerSpeed();
+
+        @Override
         public native void setPointerSpeed(int speed);
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f031b7b..3bd1e1a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2385,12 +2385,6 @@
             @StartInputReason int startInputReason,
             int unverifiedTargetSdkVersion,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-        if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid,
-                editorInfo.packageName)) {
-            Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
-                    + " uid=" + cs.mUid + " package=" + editorInfo.packageName);
-            return InputBindResult.INVALID_PACKAGE_NAME;
-        }
 
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
@@ -2521,6 +2515,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
             }
+            mSettings.putSelectedDefaultDeviceInputMethod(null);
             return defaultDeviceMethodId;
         }
 
@@ -3185,6 +3180,26 @@
                 }
             }
         }
+
+        if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+            String ime = SecureSettingsWrapper.getString(
+                    Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+            String defaultDeviceIme = SecureSettingsWrapper.getString(
+                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+            if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+                            + " device input method for user " + mSettings.getUserId()
+                            + " - restoring " + defaultDeviceIme);
+                }
+                SecureSettingsWrapper.putString(
+                        Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+                        mSettings.getUserId());
+                SecureSettingsWrapper.putString(
+                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+            }
+        }
+
         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -3659,6 +3674,15 @@
             Slog.e(TAG, "windowToken cannot be null.");
             return InputBindResult.NULL;
         }
+        // The user represented by userId, must be running.
+        if (!mUserManagerInternal.isUserRunning(userId)) {
+            // There is a chance that we hit here because of race condition. Let's just
+            // return an error code instead of crashing the caller process, which at
+            // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+            // important process.
+            Slog.w(TAG, "User #" + userId + " is not running.");
+            return InputBindResult.INVALID_USER;
+        }
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "IMMS.startInputOrWindowGainedFocus");
@@ -3666,20 +3690,105 @@
                     "InputMethodManagerService#startInputOrWindowGainedFocus");
             final InputBindResult result;
             synchronized (ImfLock.class) {
+                // If the system is not yet ready, we shouldn't be running third party code.
                 if (!mSystemReady) {
-                    // If the system is not yet ready, we shouldn't be running third arty code.
                     return new InputBindResult(
                             InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
                             null /* method */, null /* accessibilitySessions */, null /* channel */,
                             getSelectedMethodIdLocked(), getSequenceNumberLocked(),
                             false /* isInputMethodSuppressingSpellChecker */);
                 }
+                final ClientState cs = mClientController.getClient(client.asBinder());
+                if (cs == null) {
+                    throw new IllegalArgumentException("Unknown client " + client.asBinder());
+                }
                 final long ident = Binder.clearCallingIdentity();
                 try {
+                    // Verify if IMMS is in the process of switching user.
+                    if (mUserSwitchHandlerTask != null) {
+                        // There is already an on-going pending user switch task.
+                        final int nextUserId = mUserSwitchHandlerTask.mToUserId;
+                        if (userId == nextUserId) {
+                            scheduleSwitchUserTaskLocked(userId, cs.mClient);
+                            return InputBindResult.USER_SWITCHING;
+                        }
+                        final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
+                                mSettings.getUserId(), false /* enabledOnly */);
+                        for (int profileId : profileIdsWithDisabled) {
+                            if (profileId == userId) {
+                                scheduleSwitchUserTaskLocked(userId, cs.mClient);
+                                return InputBindResult.USER_SWITCHING;
+                            }
+                        }
+                        return InputBindResult.INVALID_USER;
+                    }
+
+                    // Ensure that caller's focused window and display parameters are allowd to
+                    // display input method.
+                    final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
+                            windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
+                    switch (imeClientFocus) {
+                        case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
+                            Slog.e(TAG,
+                                    "startInputOrWindowGainedFocusInternal: display ID mismatch.");
+                            return InputBindResult.DISPLAY_ID_MISMATCH;
+                        case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
+                            // Check with the window manager to make sure this client actually
+                            // has a window with focus.  If not, reject.  This is thread safe
+                            // because if the focus changes some time before or after, the
+                            // next client receiving focus that has any interest in input will
+                            // be calling through here after that change happens.
+                            if (DEBUG) {
+                                Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
+                                        + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
+                            }
+                            return InputBindResult.NOT_IME_TARGET_WINDOW;
+                        case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
+                            return InputBindResult.INVALID_DISPLAY_ID;
+                    }
+
+                    // In case mShowForced flag affects the next client to keep IME visible, when
+                    // the current client is leaving due to the next focused client, we clear
+                    // mShowForced flag when the next client's targetSdkVersion is T or higher.
+                    final boolean shouldClearFlag =
+                            mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
+                    final boolean showForced = mVisibilityStateComputer.mShowForced;
+                    if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
+                        mVisibilityStateComputer.mShowForced = false;
+                    }
+
+                    // Verify if caller is a background user.
+                    final int currentUserId = mSettings.getUserId();
+                    if (userId != currentUserId) {
+                        if (ArrayUtils.contains(
+                                mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+                            // cross-profile access is always allowed here to allow
+                            // profile-switching.
+                            scheduleSwitchUserTaskLocked(userId, cs.mClient);
+                            return InputBindResult.USER_SWITCHING;
+                        }
+                        Slog.w(TAG, "A background user is requesting window. Hiding IME.");
+                        Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+                                + " a background user, use EditorInfo.targetInputMethodUser with"
+                                + " INTERACT_ACROSS_USERS_FULL permission.");
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */,
+                                null /* resultReceiver */,
+                                SoftInputShowHideReason.HIDE_INVALID_USER);
+                        return InputBindResult.INVALID_USER;
+                    }
+
+                    if (editorInfo != null && !InputMethodUtils.checkIfPackageBelongsToUid(
+                            mPackageManagerInternal, cs.mUid, editorInfo.packageName)) {
+                        Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+                                + " uid=" + cs.mUid + " package=" + editorInfo.packageName);
+                        return InputBindResult.INVALID_PACKAGE_NAME;
+                    }
+
                     result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
                             client, windowToken, startInputFlags, softInputMode, windowFlags,
                             editorInfo, inputConnection, remoteAccessibilityInputConnection,
-                            unverifiedTargetSdkVersion, userId, imeDispatcher);
+                            unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -3708,7 +3817,7 @@
             IRemoteInputConnection inputContext,
             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
         if (DEBUG) {
             Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
                     + InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3721,86 +3830,8 @@
                     + " windowFlags=#" + Integer.toHexString(windowFlags)
                     + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
                     + " userId=" + userId
-                    + " imeDispatcher=" + imeDispatcher);
-        }
-
-        if (!mUserManagerInternal.isUserRunning(userId)) {
-            // There is a chance that we hit here because of race condition. Let's just
-            // return an error code instead of crashing the caller process, which at
-            // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
-            // important process.
-            Slog.w(TAG, "User #" + userId + " is not running.");
-            return InputBindResult.INVALID_USER;
-        }
-
-        final ClientState cs = mClientController.getClient(client.asBinder());
-        if (cs == null) {
-            throw new IllegalArgumentException("Unknown client " + client.asBinder());
-        }
-
-        final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
-                windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
-        switch (imeClientFocus) {
-            case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
-                Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch.");
-                return InputBindResult.DISPLAY_ID_MISMATCH;
-            case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
-                // Check with the window manager to make sure this client actually
-                // has a window with focus.  If not, reject.  This is thread safe
-                // because if the focus changes some time before or after, the
-                // next client receiving focus that has any interest in input will
-                // be calling through here after that change happens.
-                if (DEBUG) {
-                    Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
-                            + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
-                }
-                return InputBindResult.NOT_IME_TARGET_WINDOW;
-            case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
-                return InputBindResult.INVALID_DISPLAY_ID;
-        }
-
-        if (mUserSwitchHandlerTask != null) {
-            // There is already an on-going pending user switch task.
-            final int nextUserId = mUserSwitchHandlerTask.mToUserId;
-            if (userId == nextUserId) {
-                scheduleSwitchUserTaskLocked(userId, cs.mClient);
-                return InputBindResult.USER_SWITCHING;
-            }
-            final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
-                    mSettings.getUserId(), false /* enabledOnly */);
-            for (int profileId : profileIdsWithDisabled) {
-                if (profileId == userId) {
-                    scheduleSwitchUserTaskLocked(userId, cs.mClient);
-                    return InputBindResult.USER_SWITCHING;
-                }
-            }
-            return InputBindResult.INVALID_USER;
-        }
-
-        final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
-        // In case mShowForced flag affects the next client to keep IME visible, when the current
-        // client is leaving due to the next focused client, we clear mShowForced flag when the
-        // next client's targetSdkVersion is T or higher.
-        final boolean showForced = mVisibilityStateComputer.mShowForced;
-        if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
-            mVisibilityStateComputer.mShowForced = false;
-        }
-
-        final int currentUserId = mSettings.getUserId();
-        if (userId != currentUserId) {
-            if (ArrayUtils.contains(
-                    mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
-                // cross-profile access is always allowed here to allow profile-switching.
-                scheduleSwitchUserTaskLocked(userId, cs.mClient);
-                return InputBindResult.USER_SWITCHING;
-            }
-            Slog.w(TAG, "A background user is requesting window. Hiding IME.");
-            Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
-                    + " a background user, use EditorInfo.targetInputMethodUser with"
-                    + " INTERACT_ACROSS_USERS_FULL permission.");
-            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                    null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
-            return InputBindResult.INVALID_USER;
+                    + " imeDispatcher=" + imeDispatcher
+                    + " cs=" + cs);
         }
 
         final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
@@ -5329,7 +5360,7 @@
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
                                         mSettings.getEnabledInputMethodList());
                     mSettings.putSelectedDefaultDeviceInputMethod(
-                            newDefaultIme == null ? "" : newDefaultIme.getId());
+                            newDefaultIme == null ? null : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
                 return true;
@@ -5372,6 +5403,10 @@
 
     @GuardedBy("ImfLock.class")
     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
+        mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+        mDisplayIdToShowIme = INVALID_DISPLAY;
+        mSettings.putSelectedDefaultDeviceInputMethod(null);
+
         InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
         int lastSubtypeId = NOT_A_SUBTYPE_ID;
         // newDefaultIme is empty when there is no candidate for the selected IME.
@@ -6555,6 +6590,7 @@
 
                         // Reset selected IME.
                         settings.putSelectedInputMethod(nextIme);
+                        settings.putSelectedDefaultDeviceInputMethod(null);
                         settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                     }
                     out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002b..e444db1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.function.Predicate;
 
 /**
@@ -88,12 +87,6 @@
         mMethodMap = methodMap;
         mMethodList = methodMap.values();
         mUserId = userId;
-        String ime = getSelectedInputMethod();
-        String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
-        if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
-            putSelectedInputMethod(defaultDeviceIme);
-            putSelectedDefaultDeviceInputMethod(null);
-        }
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index 97bf354..289d4a2 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -71,31 +71,13 @@
     @Override
     public GetGeoidHeightResponse getGeoidHeight(GetGeoidHeightRequest request)
             throws RemoteException {
-        Location location = new Location("");
-        location.setLatitude(request.latitudeDegrees);
-        location.setLongitude(request.longitudeDegrees);
-        location.setAltitude(0.0);
-        location.setVerticalAccuracyMeters(0.0f);
-
-        GetGeoidHeightResponse response = new GetGeoidHeightResponse();
         try {
-            mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+            return mAltitudeConverter.getGeoidHeight(mContext, request);
         } catch (IOException e) {
+            GetGeoidHeightResponse response = new GetGeoidHeightResponse();
             response.success = false;
             return response;
         }
-        // The geoid height for a location with zero WGS84 altitude is equal in value to the
-        // negative of the corresponding MSL altitude.
-        response.geoidHeightMeters = -location.getMslAltitudeMeters();
-        // The geoid height error for a location with zero vertical accuracy is equal in value to
-        // the corresponding MSL altitude accuracy.
-        response.geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
-        // The expiration distance and additional error are currently set to constants used by
-        // health services.
-        response.expirationDistanceMeters = 10000.0;
-        response.additionalGeoidHeightErrorMeters = 0.707f;
-        response.success = true;
-        return response;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a06607b..7fb3e00 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -93,7 +93,6 @@
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.Process;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -139,11 +138,11 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.ILockSettingsStateListener;
 import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
 import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.RebootEscrowListener;
 import com.android.internal.widget.VerifyCredentialResponse;
@@ -185,6 +184,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -332,8 +332,8 @@
 
     private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
 
-    private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners =
-            new RemoteCallbackList<>();
+    private final CopyOnWriteArrayList<LockSettingsStateListener> mLockSettingsStateListeners =
+            new CopyOnWriteArrayList<>();
 
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
@@ -2379,25 +2379,12 @@
     }
 
     private void notifyLockSettingsStateListeners(boolean success, int userId) {
-        int i = mLockSettingsStateListeners.beginBroadcast();
-        try {
-            while (i > 0) {
-                i--;
-                try {
-                    if (success) {
-                        mLockSettingsStateListeners.getBroadcastItem(i)
-                                .onAuthenticationSucceeded(userId);
-                    } else {
-                        mLockSettingsStateListeners.getBroadcastItem(i)
-                                .onAuthenticationFailed(userId);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Exception while notifying LockSettingsStateListener:"
-                            + " success = " + success + ", userId = " + userId, e);
-                }
+        for (LockSettingsStateListener listener : mLockSettingsStateListeners) {
+            if (success) {
+                listener.onAuthenticationSucceeded(userId);
+            } else {
+                listener.onAuthenticationFailed(userId);
             }
-        } finally {
-            mLockSettingsStateListeners.finishBroadcast();
         }
     }
 
@@ -3720,15 +3707,15 @@
         }
 
         @Override
-        public void registerLockSettingsStateListener(
-                @NonNull ILockSettingsStateListener listener) {
-            mLockSettingsStateListeners.register(listener);
+        public void registerLockSettingsStateListener(@NonNull LockSettingsStateListener listener) {
+            Objects.requireNonNull(listener, "listener cannot be null");
+            mLockSettingsStateListeners.add(listener);
         }
 
         @Override
         public void unregisterLockSettingsStateListener(
-                @NonNull ILockSettingsStateListener listener) {
-            mLockSettingsStateListeners.unregister(listener);
+                @NonNull LockSettingsStateListener listener) {
+            mLockSettingsStateListeners.remove(listener);
         }
     }
 
diff --git a/services/core/java/com/android/server/net/watchlist/FileHashCache.java b/services/core/java/com/android/server/net/watchlist/FileHashCache.java
new file mode 100644
index 0000000..f829bc6
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/FileHashCache.java
@@ -0,0 +1,243 @@
+/*
+ * 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.net.watchlist;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @hide
+ * Utility class that keeps file hashes in cache. This cache is persistent across reboots.
+ * If requested hash does not exist in cache, it is calculated from the target file. Cache gets
+ * persisted once it is changed in deferred mode to prevent multiple savings per many small updates.
+ * Deleted files are detected and removed from the cache during the initial load. If file change is
+ * detected, it is hash is calculated during the next request.
+ * The synchronization is done using Handler. All requests for hashes must be done in context of
+ * handler thread.
+ */
+public class FileHashCache {
+    private static final String TAG = FileHashCache.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    // Turns on the check that validates hash in cache matches one, calculated directly on the
+    // target file. Not to be used in production.
+    private static final boolean VERIFY = false;
+
+    // Used for logging wtf only once during load, see logWtfOnce()
+    private static boolean sLoggedWtf = false;
+
+    @VisibleForTesting
+    static String sPersistFileName = "/data/system/file_hash_cache";
+
+    static long sSaveDeferredDelayMillis = TimeUnit.SECONDS.toMillis(5);
+
+    private static class Entry {
+        public final long mLastModified;
+        public final byte[] mSha256Hash;
+
+        Entry(long lastModified, @NonNull byte[] sha256Hash) {
+            mLastModified = lastModified;
+            mSha256Hash = sha256Hash;
+        }
+    }
+
+    private Handler mHandler;
+    private final Map<File, Entry> mEntries = new HashMap<>();
+
+    private final Runnable mLoadTask = () -> {
+        load();
+    };
+    private final Runnable mSaveTask = () -> {
+        save();
+    };
+
+    /**
+     * @hide
+     */
+    public FileHashCache(@NonNull Handler handler) {
+        mHandler = handler;
+        mHandler.post(mLoadTask);
+    }
+
+    /**
+     * Requests sha256 for the provided file from the cache. If cache entry does not exist or
+     * file was modified, then null is returned.
+     * @hide
+    **/
+    @VisibleForTesting
+    @Nullable
+    byte[] getSha256HashFromCache(@NonNull File file) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            Slog.wtf(TAG, "Request from invalid thread", new Exception());
+            return null;
+        }
+
+        final Entry entry = mEntries.get(file);
+        if (entry == null) {
+            return null;
+        }
+
+        try {
+            if (entry.mLastModified == Os.stat(file.getAbsolutePath()).st_ctime) {
+                if (VERIFY) {
+                    try {
+                        if (!Arrays.equals(entry.mSha256Hash, DigestUtils.getSha256Hash(file))) {
+                            Slog.wtf(TAG, "Failed to verify entry for " + file);
+                        }
+                    } catch (NoSuchAlgorithmException | IOException e) { }
+                }
+
+                return entry.mSha256Hash;
+            }
+        } catch (ErrnoException e) { }
+
+        if (DEBUG) Slog.v(TAG, "Found stale cached entry for " + file);
+        mEntries.remove(file);
+        return null;
+    }
+
+    /**
+     * Requests sha256 for the provided file. If cache entry does not exist or file was modified,
+     * hash is calculated from the requested file. Otherwise hash from cache is returned.
+     * @hide
+    **/
+    @NonNull
+    public byte[] getSha256Hash(@NonNull File file) throws NoSuchAlgorithmException, IOException {
+        byte[] sha256Hash = getSha256HashFromCache(file);
+        if (sha256Hash != null) {
+            return sha256Hash;
+        }
+
+        try {
+            sha256Hash = DigestUtils.getSha256Hash(file);
+            mEntries.put(file, new Entry(Os.stat(file.getAbsolutePath()).st_ctime, sha256Hash));
+            if (DEBUG) Slog.v(TAG, "New cache entry is created for " + file);
+            scheduleSave();
+            return sha256Hash;
+        } catch (ErrnoException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private static void closeQuietly(@Nullable Closeable closeable) {
+        try {
+            if (closeable != null) {
+                closeable.close();
+            }
+        } catch (IOException e) { }
+    }
+
+    /**
+     * Log an error as wtf only the first instance, then log as warning.
+     */
+    private static void logWtfOnce(@NonNull final String s, final Exception e) {
+        if (!sLoggedWtf) {
+            Slog.wtf(TAG, s, e);
+            sLoggedWtf = true;
+        } else {
+            Slog.w(TAG, s, e);
+        }
+    }
+
+    private void load() {
+        mEntries.clear();
+
+        final long startTime = SystemClock.currentTimeMicro();
+        final File file = new File(sPersistFileName);
+        if (!file.exists()) {
+            if (DEBUG) Slog.v(TAG, "Storage file does not exist. Starting from scratch.");
+            return;
+        }
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(file));
+            // forEach rethrows IOException as UncheckedIOException
+            reader.lines().forEach((fileEntry)-> {
+                try {
+                    final StringTokenizer tokenizer = new StringTokenizer(fileEntry, ",");
+                    final File testFile = new File(tokenizer.nextToken());
+                    final long lastModified = Long.parseLong(tokenizer.nextToken());
+                    final byte[] sha256 = HexDump.hexStringToByteArray(tokenizer.nextToken());
+                    mEntries.put(testFile, new Entry(lastModified, sha256));
+                    if (DEBUG) Slog.v(TAG, "Loaded entry for " + testFile);
+                } catch (RuntimeException e) {
+                    // hexStringToByteArray can throw raw RuntimeException on invalid input.  Avoid
+                    // potentially reporting one error per line if the data is corrupt.
+                    logWtfOnce("Invalid entry for " + fileEntry, e);
+                    return;
+                }
+            });
+            if (DEBUG) {
+                Slog.i(TAG, "Loaded " + mEntries.size() + " entries in "
+                        + (SystemClock.currentTimeMicro() - startTime) + " mcs.");
+            }
+        } catch (IOException | UncheckedIOException e) {
+            Slog.e(TAG, "Failed to read storage file", e);
+        } finally {
+            closeQuietly(reader);
+        }
+    }
+
+    private void scheduleSave() {
+        mHandler.removeCallbacks(mSaveTask);
+        mHandler.postDelayed(mSaveTask, sSaveDeferredDelayMillis);
+    }
+
+    private void save() {
+        BufferedWriter writer = null;
+        final long startTime = SystemClock.currentTimeMicro();
+        try {
+            writer = new BufferedWriter(new FileWriter(sPersistFileName));
+            for (Map.Entry<File, Entry> entry : mEntries.entrySet()) {
+                writer.write(entry.getKey() + ","
+                             + entry.getValue().mLastModified + ","
+                             + HexDump.toHexString(entry.getValue().mSha256Hash) + "\n");
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "Saved " + mEntries.size() + " entries in "
+                        + (SystemClock.currentTimeMicro() - startTime) + " mcs.");
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to save.", e);
+        } finally {
+            closeQuietly(writer);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
index 8ce7b57..c863cbf 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -83,6 +83,8 @@
     private final ConcurrentHashMap<Integer, byte[]> mCachedUidDigestMap =
             new ConcurrentHashMap<>();
 
+    private final FileHashCache mApkHashCache;
+
     private interface WatchlistEventKeys {
         String HOST = "host";
         String IP_ADDRESSES = "ipAddresses";
@@ -100,6 +102,13 @@
         mSettings = WatchlistSettings.getInstance();
         mDropBoxManager = mContext.getSystemService(DropBoxManager.class);
         mPrimaryUserId = getPrimaryUserId();
+        if (context.getResources().getBoolean(
+                com.android.internal.R.bool.config_watchlistUseFileHashesCache)) {
+            mApkHashCache = new FileHashCache(this);
+            Slog.i(TAG, "Using file hashes cache.");
+        } else {
+            mApkHashCache = null;
+        }
     }
 
     @Override
@@ -345,7 +354,9 @@
                             Slog.i(TAG, "Skipping incremental path: " + packageName);
                             continue;
                         }
-                        return DigestUtils.getSha256Hash(new File(apkPath));
+                        return mApkHashCache != null
+                                ? mApkHashCache.getSha256Hash(new File(apkPath))
+                                : DigestUtils.getSha256Hash(new File(apkPath));
                     } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
                         Slog.e(TAG, "Cannot get digest from uid: " + key
                                 + ",pkg: " + packageName, e);
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index dff02bf..e349fa3 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.COLOR_DEFAULT;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_GROUP_SUMMARY;
@@ -23,15 +24,24 @@
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 
 import android.annotation.NonNull;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * NotificationManagerService helper for auto-grouping notifications.
@@ -41,6 +51,8 @@
 
     protected static final String AUTOGROUP_KEY = "ranker_group";
 
+    protected static final int FLAG_INVALID = -1;
+
     // Flags that all autogroup summaries have
     protected static final int BASE_FLAGS =
             FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
@@ -51,17 +63,22 @@
 
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
+    private final Context mContext;
+    private final PackageManager mPackageManager;
 
     // Only contains notifications that are not explicitly grouped by the app (aka no group or
     // sort key).
     // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
     @GuardedBy("mUngroupedNotifications")
-    private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+    private final ArrayMap<String, ArrayMap<String, NotificationAttributes>> mUngroupedNotifications
             = new ArrayMap<>();
 
-    public GroupHelper(int autoGroupAtCount, Callback callback) {
+    public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
+            Callback callback) {
         mAutoGroupAtCount = autoGroupAtCount;
         mCallback =  callback;
+        mContext = context;
+        mPackageManager = packageManager;
     }
 
     private String generatePackageKey(int userId, String pkg) {
@@ -70,15 +87,16 @@
 
     @VisibleForTesting
     @GuardedBy("mUngroupedNotifications")
-    protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+    protected int getAutogroupSummaryFlags(
+            @NonNull final ArrayMap<String, NotificationAttributes> children) {
         boolean allChildrenHasFlag = children.size() > 0;
         int anyChildFlagSet = 0;
         for (int i = 0; i < children.size(); i++) {
-            if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+            if (!hasAnyFlag(children.valueAt(i).flags, ALL_CHILDREN_FLAG)) {
                 allChildrenHasFlag = false;
             }
-            if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
-                anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+            if (hasAnyFlag(children.valueAt(i).flags, ANY_CHILDREN_FLAGS)) {
+                anyChildFlagSet |= (children.valueAt(i).flags & ANY_CHILDREN_FLAGS);
             }
         }
         return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
@@ -95,7 +113,6 @@
             } else {
                 maybeUngroup(sbn, false, sbn.getUserId());
             }
-
         } catch (Exception e) {
             Slog.e(TAG, "Failure processing new notification", e);
         }
@@ -121,25 +138,47 @@
     private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
         int flags = 0;
         List<String> notificationsToGroup = new ArrayList<>();
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
         synchronized (mUngroupedNotifications) {
             String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-            final ArrayMap<String, Integer> children =
+            final ArrayMap<String, NotificationAttributes> children =
                     mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
 
-            children.put(sbn.getKey(), sbn.getNotification().flags);
+            NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
+                    sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+            children.put(sbn.getKey(), attr);
             mUngroupedNotifications.put(key, children);
 
             if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
                 flags = getAutogroupSummaryFlags(children);
                 notificationsToGroup.addAll(children.keySet());
+                childrenAttr.addAll(children.values());
             }
         }
         if (notificationsToGroup.size() > 0) {
             if (autogroupSummaryExists) {
-                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+                NotificationAttributes attr = new NotificationAttributes(flags,
+                        sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+                if (Flags.autogroupSummaryIconUpdate()) {
+                    attr = updateAutobundledSummaryIcon(sbn.getPackageName(), childrenAttr, attr);
+                }
+
+                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), attr);
             } else {
-                mCallback.addAutoGroupSummary(
-                        sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+                Icon summaryIcon = sbn.getNotification().getSmallIcon();
+                int summaryIconColor = sbn.getNotification().color;
+                if (Flags.autogroupSummaryIconUpdate()) {
+                    // Calculate the initial summary icon and icon color
+                    NotificationAttributes iconAttr = getAutobundledSummaryIconAndColor(
+                            sbn.getPackageName(), childrenAttr);
+                    summaryIcon = iconAttr.icon;
+                    summaryIconColor = iconAttr.iconColor;
+                }
+
+                NotificationAttributes attr = new NotificationAttributes(flags, summaryIcon,
+                        summaryIconColor);
+                mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
+                        attr);
             }
             for (String key : notificationsToGroup) {
                 mCallback.addAutoGroup(key);
@@ -154,16 +193,17 @@
      * (b) if we need to remove our autogroup overlay for this notification
      * (c) we need to remove the autogroup summary
      *
-     * And updates the internal state of un-app-grouped notifications and their flags
+     * And updates the internal state of un-app-grouped notifications and their flags.
      */
     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
         boolean removeSummary = false;
-        int summaryFlags = 0;
+        int summaryFlags = FLAG_INVALID;
         boolean updateSummaryFlags = false;
         boolean removeAutogroupOverlay = false;
+        List<NotificationAttributes> childrenAttrs = new ArrayList<>();
         synchronized (mUngroupedNotifications) {
             String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-            final ArrayMap<String, Integer> children =
+            final ArrayMap<String, NotificationAttributes> children =
                     mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
             if (children.size() == 0) {
                 return;
@@ -173,7 +213,7 @@
             if (children.containsKey(sbn.getKey())) {
                 // if this notification was contributing flags that aren't covered by other
                 // children to the summary, reevaluate flags for the summary
-                int flags = children.remove(sbn.getKey());
+                int flags = children.remove(sbn.getKey()).flags;
                 // this
                 if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
                     updateSummaryFlags = true;
@@ -188,14 +228,29 @@
                 // If there are no more children left to autogroup, remove the summary
                 if (children.size() == 0) {
                     removeSummary = true;
+                } else {
+                    childrenAttrs.addAll(children.values());
                 }
             }
         }
+
         if (removeSummary) {
             mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
         } else {
-            if (updateSummaryFlags) {
-                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+            NotificationAttributes attr = new NotificationAttributes(summaryFlags,
+                    sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+            boolean iconUpdated = false;
+            if (Flags.autogroupSummaryIconUpdate()) {
+                NotificationAttributes newAttr = updateAutobundledSummaryIcon(sbn.getPackageName(),
+                        childrenAttrs, attr);
+                if (!newAttr.equals(attr)) {
+                    iconUpdated = true;
+                    attr = newAttr;
+                }
+            }
+
+            if (updateSummaryFlags || iconUpdated) {
+                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), attr);
             }
         }
         if (removeAutogroupOverlay) {
@@ -207,17 +262,139 @@
     int getNotGroupedByAppCount(int userId, String pkg) {
         synchronized (mUngroupedNotifications) {
             String key = generatePackageKey(userId, pkg);
-            final ArrayMap<String, Integer> children =
+            final ArrayMap<String, NotificationAttributes> children =
                     mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
             return children.size();
         }
     }
 
+    NotificationAttributes getAutobundledSummaryIconAndColor(@NonNull String packageName,
+            @NonNull List<NotificationAttributes> childrenAttr) {
+        Icon newIcon = null;
+        boolean childrenHaveSameIcon = true;
+        int newColor = Notification.COLOR_INVALID;
+        boolean childrenHaveSameColor = true;
+
+        // Both the icon drawable and the icon background color are updated according to this rule:
+        // - if all child icons are identical => use the common icon
+        // - if child icons are different: use the monochromatic app icon, if exists.
+        // Otherwise fall back to a generic icon representing a stack.
+        for (NotificationAttributes state: childrenAttr) {
+            // Check for icon
+            if (newIcon == null) {
+                newIcon = state.icon;
+            } else {
+                if (!newIcon.sameAs(state.icon)) {
+                    childrenHaveSameIcon = false;
+                }
+            }
+            // Check for color
+            if (newColor == Notification.COLOR_INVALID) {
+                newColor = state.iconColor;
+            } else {
+                if (newColor != state.iconColor) {
+                    childrenHaveSameColor = false;
+                }
+            }
+        }
+        if (!childrenHaveSameIcon) {
+            newIcon = getMonochromeAppIcon(packageName);
+        }
+        if (!childrenHaveSameColor) {
+            newColor = COLOR_DEFAULT;
+        }
+
+        return new NotificationAttributes(0, newIcon, newColor);
+    }
+
+    NotificationAttributes updateAutobundledSummaryIcon(@NonNull String packageName,
+            @NonNull List<NotificationAttributes> childrenAttr,
+            @NonNull NotificationAttributes oldAttr) {
+        NotificationAttributes newAttr = getAutobundledSummaryIconAndColor(packageName,
+                childrenAttr);
+        Icon newIcon = newAttr.icon;
+        int newColor = newAttr.iconColor;
+        if (newAttr.icon == null) {
+            newIcon = oldAttr.icon;
+        }
+        if (newAttr.iconColor == Notification.COLOR_INVALID) {
+            newColor = oldAttr.iconColor;
+        }
+
+        return new NotificationAttributes(oldAttr.flags, newIcon, newColor);
+    }
+
+    /**
+     * Get the monochrome app icon for an app from the adaptive launcher icon
+     *  or a fallback generic icon for autogroup summaries.
+     *
+     * @param pkg packageName of the app
+     * @return a monochrome app icon or a fallback generic icon
+     */
+    @NonNull
+    Icon getMonochromeAppIcon(@NonNull final String pkg) {
+        Icon monochromeIcon = null;
+        final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+        try {
+            final Drawable appIcon = mPackageManager.getApplicationIcon(pkg);
+            if (appIcon instanceof AdaptiveIconDrawable) {
+                if (((AdaptiveIconDrawable) appIcon).getMonochrome() != null) {
+                    monochromeIcon = Icon.createWithResourceAdaptiveDrawable(pkg,
+                            ((AdaptiveIconDrawable) appIcon).getSourceDrawableResId(), true,
+                            -2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
+                }
+            }
+        } catch (NameNotFoundException e) {
+            Slog.e(TAG, "Failed to getApplicationIcon() in getMonochromeAppIcon()", e);
+        }
+        if (monochromeIcon != null) {
+            return monochromeIcon;
+        } else {
+            return Icon.createWithResource(mContext, fallbackIconResId);
+        }
+    }
+
+    protected static class NotificationAttributes {
+        public final int flags;
+        public final int iconColor;
+        public final Icon icon;
+
+        public NotificationAttributes(int flags, Icon icon, int iconColor) {
+            this.flags = flags;
+            this.icon = icon;
+            this.iconColor = iconColor;
+        }
+
+        public NotificationAttributes(@NonNull NotificationAttributes attr) {
+            this.flags = attr.flags;
+            this.icon = attr.icon;
+            this.iconColor = attr.iconColor;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof NotificationAttributes that)) {
+                return false;
+            }
+            return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(flags, iconColor, icon);
+        }
+    }
+
     protected interface Callback {
         void addAutoGroup(String key);
         void removeAutoGroup(String key);
-        void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
+
+        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+                NotificationAttributes summaryAttr);
         void removeAutoGroupSummary(int user, String pkg);
-        void updateAutogroupSummary(int userId, String pkg, int flags);
+        void updateAutogroupSummary(int userId, String pkg, NotificationAttributes summaryAttr);
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index f852b81..097daf2 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -97,7 +97,7 @@
     private static final float DEFAULT_VOLUME = 1.0f;
     // TODO (b/291899544): remove for release
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
-    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
+    private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 1;
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
 
@@ -1405,6 +1405,7 @@
                 long timestampMillis) {
             super.setLastNotificationUpdateTimeMs(record, timestampMillis);
             mLastNotificationTimestamp = timestampMillis;
+            mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis);
         }
 
         long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4da2cc9..91706cf 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -227,6 +227,7 @@
 import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
@@ -341,6 +342,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
 import com.android.server.notification.toast.CustomToastRecord;
@@ -996,17 +998,19 @@
     }
 
     /**
-     * This method will update the flags of the summary.
+     * This method will update the flags and/or the icon of the summary.
      * It will set it to FLAG_ONGOING_EVENT if any of its group members
-     * has the same flag. It will delete the flag otherwise
+     * has the same flag. It will delete the flag otherwise.
+     * It will update the summary notification icon if the group children's
+     * icons are different.
      * @param userId user id of the autogroup summary
      * @param pkg package of the autogroup summary
-     * @param flags the new flags for this summary
+     * @param summaryAttr the new flags and/or icon & color for this summary
      * @param isAppForeground true if the app is currently in the foreground.
      */
     @GuardedBy("mNotificationLock")
-    protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
-            boolean isAppForeground) {
+    protected void updateAutobundledSummaryLocked(int userId, String pkg,
+            NotificationAttributes summaryAttr, boolean isAppForeground) {
         ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
         if (summaries == null) {
             return;
@@ -1020,8 +1024,16 @@
             return;
         }
         int oldFlags = summary.getSbn().getNotification().flags;
-        if (oldFlags != flags) {
-            summary.getSbn().getNotification().flags = flags;
+
+        boolean iconUpdated =
+                !summaryAttr.icon.sameAs(summary.getSbn().getNotification().getSmallIcon())
+                || summaryAttr.iconColor != summary.getSbn().getNotification().color;
+
+        if (oldFlags != summaryAttr.flags || iconUpdated) {
+            summary.getSbn().getNotification().flags =
+                    summaryAttr.flags != GroupHelper.FLAG_INVALID ? summaryAttr.flags : oldFlags;
+            summary.getSbn().getNotification().setSmallIcon(summaryAttr.icon);
+            summary.getSbn().getNotification().color = summaryAttr.iconColor;
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
                     mPostNotificationTrackerFactory.newTracker(null)));
         }
@@ -2049,7 +2061,9 @@
                 if (!mUserProfiles.isProfileUser(userId)) {
                     mConditionProviders.onUserUnlocked(userId);
                     mListeners.onUserUnlocked(userId);
-                    mZenModeHelper.onUserUnlocked(userId);
+                    if (!android.app.Flags.modesApi()) {
+                        mZenModeHelper.onUserUnlocked(userId);
+                    }
                 }
             }
         }
@@ -2871,7 +2885,8 @@
     private GroupHelper getGroupHelper() {
         mAutoGroupAtCount =
                 getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
-        return new GroupHelper(mAutoGroupAtCount, new GroupHelper.Callback() {
+        return new GroupHelper(getContext(), getContext().getPackageManager(),
+                mAutoGroupAtCount, new GroupHelper.Callback() {
             @Override
             public void addAutoGroup(String key) {
                 synchronized (mNotificationLock) {
@@ -2888,8 +2903,9 @@
 
             @Override
             public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                    int flags) {
-                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+                    NotificationAttributes summaryAttr) {
+                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey,
+                        summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor);
                 if (r != null) {
                     final boolean isAppForeground =
                             mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -2906,11 +2922,12 @@
             }
 
             @Override
-            public void updateAutogroupSummary(int userId, String pkg, int flags) {
+            public void updateAutogroupSummary(int userId, String pkg,
+                    NotificationAttributes summaryAttr) {
                 boolean isAppForeground = pkg != null
                         && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                 synchronized (mNotificationLock) {
-                    updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
+                    updateAutobundledSummaryLocked(userId, pkg, summaryAttr, isAppForeground);
                 }
             }
         });
@@ -4878,14 +4895,14 @@
                                 continue;
                             }
                             notificationsRapidlyCleared = notificationsRapidlyCleared
-                                    || isNotificationRecent(r);
+                                    || isNotificationRecent(r.getUpdateTimeMs());
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                     r.getSbn().getPackageName(), r.getSbn().getTag(),
                                     r.getSbn().getId(), userId, reason);
                         }
                     } else {
                         for (NotificationRecord notificationRecord : mNotificationList) {
-                            if (isNotificationRecent(notificationRecord)) {
+                            if (isNotificationRecent(notificationRecord.getUpdateTimeMs())) {
                                 notificationsRapidlyCleared = true;
                                 break;
                             }
@@ -4911,14 +4928,6 @@
             }
         }
 
-        private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) {
-            if (!rapidClearNotificationsByListenerAppOpEnabled()) {
-                return false;
-            }
-            return notificationRecord.getFreshnessMs(System.currentTimeMillis())
-                    < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
-        }
-
         /**
          * Handle request from an approved listener to re-enable itself.
          *
@@ -5042,12 +5051,11 @@
         @Override
         public void snoozeNotificationUntilContextFromListener(INotificationListener token,
                 String key, String snoozeCriterionId) {
+            final int callingUid = Binder.getCallingUid();
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationLock) {
-                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                    snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
-                }
+                snoozeNotificationInt(callingUid, token, key, SNOOZE_UNTIL_UNSPECIFIED,
+                        snoozeCriterionId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -5061,12 +5069,10 @@
         @Override
         public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
                 long duration) {
+            final int callingUid = Binder.getCallingUid();
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationLock) {
-                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                    snoozeNotificationInt(key, duration, null, info);
-                }
+                snoozeNotificationInt(callingUid, token, key, duration, null);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -6527,7 +6533,7 @@
 
     // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
     NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            int flagsToSet) {
+            int flagsToSet, Icon summaryIcon, int summaryIconColor) {
         NotificationRecord summaryRecord = null;
         boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         synchronized (mNotificationLock) {
@@ -6553,14 +6559,15 @@
                 final Bundle extras = new Bundle();
                 extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
                 final String channelId = notificationRecord.getChannel().getId();
+
                 final Notification summaryNotification =
-                        new Notification.Builder(getContext(), channelId)
-                                .setSmallIcon(adjustedSbn.getNotification().getSmallIcon())
+                                new Notification.Builder(getContext(), channelId)
+                                .setSmallIcon(summaryIcon)
                                 .setGroupSummary(true)
                                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
                                 .setFlag(flagsToSet, true)
-                                .setColor(adjustedSbn.getNotification().color)
+                                .setColor(summaryIconColor)
                                 .build();
                 summaryNotification.extras.putAll(extras);
                 Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -7953,8 +7960,8 @@
                     && mTelecomManager != null) {
                 try {
                     return mTelecomManager.isInManagedCall()
-                            || mTelecomManager.isInSelfManagedCall(
-                            pkg, UserHandle.getUserHandleForUid(uid));
+                            || mTelecomManager.isInSelfManagedCall(pkg,
+                            UserHandle.getUserHandleForUid(uid), /* hasCrossUserAccess */ true);
                 } catch (IllegalStateException ise) {
                     // Telecom is not ready (this is likely early boot), so there are no calls.
                     return false;
@@ -8842,19 +8849,26 @@
         }
     }
 
+    private PendingIntent getNotificationTimeoutPendingIntent(NotificationRecord record,
+            int flags) {
+        flags |= PendingIntent.FLAG_IMMUTABLE;
+        return PendingIntent.getBroadcast(getContext(),
+                REQUEST_CODE_TIMEOUT,
+                new Intent(ACTION_NOTIFICATION_TIMEOUT)
+                        .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+                        .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+                                .appendPath(record.getKey()).build())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                        .putExtra(EXTRA_KEY, record.getKey()),
+                flags);
+    }
+
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
     void scheduleTimeoutLocked(NotificationRecord record) {
         if (record.getNotification().getTimeoutAfter() > 0) {
-            final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
-                    REQUEST_CODE_TIMEOUT,
-                    new Intent(ACTION_NOTIFICATION_TIMEOUT)
-                            .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
-                            .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
-                                    .appendPath(record.getKey()).build())
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                            .putExtra(EXTRA_KEY, record.getKey()),
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            final PendingIntent pi = getNotificationTimeoutPendingIntent(
+                    record, PendingIntent.FLAG_UPDATE_CURRENT);
             mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
         }
@@ -8862,6 +8876,16 @@
 
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
+    void cancelScheduledTimeoutLocked(NotificationRecord record) {
+        final PendingIntent pi = getNotificationTimeoutPendingIntent(
+                record, PendingIntent.FLAG_CANCEL_CURRENT);
+        if (pi != null) {
+            mAlarmManager.cancel(pi);
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mNotificationLock")
     /**
      * Determine whether this notification should attempt to make noise, vibrate, or flash the LED
      * @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
@@ -9892,21 +9916,7 @@
             int rank, int count, boolean wasPosted, String listenerName,
             @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         final String canceledKey = r.getKey();
-
-        // Get pending intent used to create alarm, use FLAG_NO_CREATE if PendingIntent
-        // does not already exist, then null will be returned.
-        final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
-                REQUEST_CODE_TIMEOUT,
-                new Intent(ACTION_NOTIFICATION_TIMEOUT)
-                        .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
-                                .appendPath(r.getKey()).build())
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
-
-        // Cancel alarm corresponding to pi.
-        if (pi != null) {
-            mAlarmManager.cancel(pi);
-        }
+        cancelScheduledTimeoutLocked(r);
 
         // Record caller.
         recordCallerLocked(r);
@@ -10321,16 +10331,22 @@
         }
     }
 
-    void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
-            ManagedServiceInfo listener) {
-        if (listener == null) {
-            return;
-        }
-        String listenerName = listener.component.toShortString();
-        if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
-            return;
-        }
+    void snoozeNotificationInt(int callingUid, INotificationListener token, String key,
+            long duration, String snoozeCriterionId) {
+        final String packageName;
+        final long notificationUpdateTimeMs;
+
         synchronized (mNotificationLock) {
+            final ManagedServiceInfo listener = mListeners.checkServiceTokenLocked(token);
+            if (listener == null) {
+                return;
+            }
+            packageName = listener.component.getPackageName();
+            String listenerName = listener.component.toShortString();
+            if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
+                return;
+            }
+
             final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
             if (r == null) {
                 return;
@@ -10338,14 +10354,20 @@
             if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
                 return;
             }
+            notificationUpdateTimeMs = r.getUpdateTimeMs();
+
+            if (DBG) {
+                Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
+                        snoozeCriterionId, listenerName));
+            }
+            // Needs to post so that it can cancel notifications not yet enqueued.
+            mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
         }
 
-        if (DBG) {
-            Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
-                    snoozeCriterionId, listenerName));
+        if (isNotificationRecent(notificationUpdateTimeMs)) {
+            mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+                    callingUid, packageName, /* attributionTag= */ null, /* message= */ null);
         }
-        // Needs to post so that it can cancel notifications not yet enqueued.
-        mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
     }
 
     void unsnoozeNotificationInt(String key, ManagedServiceInfo listener, boolean muteOnReturn) {
@@ -10357,6 +10379,14 @@
         handleSavePolicyFile();
     }
 
+    private boolean isNotificationRecent(long notificationUpdateTimeMs) {
+        if (!rapidClearNotificationsByListenerAppOpEnabled()) {
+            return false;
+        }
+        return System.currentTimeMillis() - notificationUpdateTimeMs
+                < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
+    }
+
     @GuardedBy("mNotificationLock")
     void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
             ManagedServiceInfo listener, boolean includeCurrentProfiles, int mustNotHaveFlags) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1786ac5..6ab4b99 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -308,31 +308,43 @@
         return light;
     }
 
+    private VibrationEffect getVibrationForChannel(
+            NotificationChannel channel, VibratorHelper helper, boolean insistent) {
+        if (!channel.shouldVibrate()) {
+            return null;
+        }
+
+        if (Flags.notificationChannelVibrationEffectApi()) {
+            final VibrationEffect vibration = channel.getVibrationEffect();
+            if (vibration != null && helper.areEffectComponentsSupported(vibration)) {
+                // Adjust the vibration's repeat behavior based on the `insistent` property.
+                return vibration.applyRepeatingIndefinitely(insistent, /* loopDelayMs= */ 0);
+            }
+        }
+
+        final long[] vibrationPattern = channel.getVibrationPattern();
+        if (vibrationPattern == null) {
+            return helper.createDefaultVibration(insistent);
+        }
+        return helper.createWaveformVibration(vibrationPattern, insistent);
+    }
+
     private VibrationEffect calculateVibration() {
         VibratorHelper helper = new VibratorHelper(mContext);
         final Notification notification = getSbn().getNotification();
         final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0;
-        VibrationEffect defaultVibration = helper.createDefaultVibration(insistent);
-        VibrationEffect vibration;
-        if (getChannel().shouldVibrate()) {
-            vibration = getChannel().getVibrationPattern() == null
-                    ? defaultVibration
-                    : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent);
-        } else {
-            vibration = null;
-        }
+
         if (mPreChannelsNotification
                 && (getChannel().getUserLockedFields()
                 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
             final boolean useDefaultVibrate =
                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
             if (useDefaultVibrate) {
-                vibration = defaultVibration;
-            } else {
-                vibration = helper.createWaveformVibration(notification.vibrate, insistent);
+                return helper.createDefaultVibration(insistent);
             }
+            return  helper.createWaveformVibration(notification.vibrate, insistent);
         }
-        return vibration;
+        return getVibrationForChannel(getChannel(), helper, insistent);
     }
 
     private @NonNull AudioAttributes calculateAttributes() {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bafcfe..4f3cdbc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1212,6 +1212,8 @@
         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0
                 && (!Arrays.equals(oldParent.getVibrationPattern(),
                 updatedParent.getVibrationPattern())
+                || !Objects.equals(
+                        oldParent.getVibrationEffect(), updatedParent.getVibrationEffect())
                 || oldParent.shouldVibrate() != updatedParent.shouldVibrate())) {
             // enableVibration must be 2nd because setVibrationPattern may toggle it.
             conversation.setVibrationPattern(updatedParent.getVibrationPattern());
@@ -1972,6 +1974,7 @@
             update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
         }
         if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
+                || !Objects.equals(original.getVibrationEffect(), update.getVibrationEffect())
                 || original.shouldVibrate() != update.shouldVibrate()) {
             update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
         }
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 7204d05..8a0e595 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -193,6 +193,11 @@
         return createWaveformVibration(mDefaultPattern, insistent);
     }
 
+    /** Returns if a given vibration can be played by the vibrator that does notification buzz. */
+    public boolean areEffectComponentsSupported(VibrationEffect effect) {
+        return mVibrator.areVibrationFeaturesSupported(effect);
+    }
+
     @Nullable
     private static float[] getFloatArray(Resources resources, int resId) {
         TypedArray array = resources.obtainTypedArray(resId);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 41ff415..2f20bbe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -328,6 +328,7 @@
         }
     }
 
+    // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
     public void onUserUnlocked(int user) {
         loadConfigForUser(user, "onUserUnlocked");
     }
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dbff442..722654a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -43,6 +43,13 @@
 }
 
 flag {
+    name: "screenshare_notification_hiding"
+    namespace: "systemui"
+    description: "Enable hiding of notifications during screenshare"
+    bug: "312784809"
+}
+
+flag {
   name: "sensitive_notification_app_protection"
   namespace: "systemui"
   description: "This flag controls the sensitive notification app protections while screen sharing"
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 86d05d9..25a39cc 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
     private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
             @NonNull AndroidPackage overlayPackage, int userId) {
         String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
-        if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
+        if (targetOverlayableName != null) {
             try {
                 OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
                         targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a61b03f..b9464d9 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,7 +32,6 @@
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
 import static android.os.Trace.traceEnd;
-
 import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
 
 import android.annotation.NonNull;
@@ -363,7 +362,7 @@
                 defaultPackages.add(packageName);
             }
         }
-        return defaultPackages.toArray(new String[0]);
+        return defaultPackages.toArray(new String[defaultPackages.size()]);
     }
 
     private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1144,10 +1143,9 @@
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
-        private static final class PackageStateUsers {
+        private static class PackageStateUsers {
             private PackageState mPackageState;
-            private Boolean mDefinesOverlayable = null;
-            private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
+            private final Set<Integer> mInstalledUsers = new ArraySet<>();
             private PackageStateUsers(@NonNull PackageState packageState) {
                 this.mPackageState = packageState;
             }
@@ -1162,7 +1160,7 @@
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
         private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
-        private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
+        private final Set<Integer> mInitializedUsers = new ArraySet<>();
 
         PackageManagerHelperImpl(Context context) {
             mContext = context;
@@ -1178,7 +1176,8 @@
          */
         @NonNull
         public ArrayMap<String, PackageState> initializeForUser(final int userId) {
-            if (mInitializedUsers.add(userId)) {
+            if (!mInitializedUsers.contains(userId)) {
+                mInitializedUsers.add(userId);
                 mPackageManagerInternal.forEachPackageState((packageState -> {
                     if (packageState.getPkg() != null
                             && packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1197,11 +1196,13 @@
             return userPackages;
         }
 
-        private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
+        @Override
+        @Nullable
+        public PackageState getPackageStateForUser(@NonNull final String packageName,
                 final int userId) {
             final PackageStateUsers pkg = mCache.get(packageName);
             if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
-                return pkg;
+                return pkg.mPackageState;
             }
             try {
                 if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1215,14 +1216,8 @@
             return addPackageUser(packageName, userId);
         }
 
-        @Override
-        public PackageState getPackageStateForUser(@NonNull final String packageName,
-                final int userId) {
-            final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId);
-            return pkg != null ? pkg.mPackageState : null;
-        }
-
-        private PackageStateUsers addPackageUser(@NonNull final String packageName,
+        @NonNull
+        private PackageState addPackageUser(@NonNull final String packageName,
                 final int user) {
             final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
             if (pkg == null) {
@@ -1234,20 +1229,20 @@
         }
 
         @NonNull
-        private PackageStateUsers addPackageUser(@NonNull final PackageState pkg,
+        private PackageState addPackageUser(@NonNull final PackageState pkg,
                 final int user) {
             PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
             if (pkgUsers == null) {
                 pkgUsers = new PackageStateUsers(pkg);
                 mCache.put(pkg.getPackageName(), pkgUsers);
-            } else if (pkgUsers.mPackageState != pkg) {
+            } else {
                 pkgUsers.mPackageState = pkg;
-                pkgUsers.mDefinesOverlayable = null;
             }
             pkgUsers.mInstalledUsers.add(user);
-            return pkgUsers;
+            return pkgUsers.mPackageState;
         }
 
+
         @NonNull
         private void removePackageUser(@NonNull final String packageName, final int user) {
             final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1265,15 +1260,15 @@
             }
         }
 
+        @Nullable
         public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
-            final var pu = addPackageUser(packageName, userId);
-            return pu != null ? pu.mPackageState : null;
+            return addPackageUser(packageName, userId);
         }
 
+        @Nullable
         public PackageState onPackageUpdated(@NonNull final String packageName,
                 final int userId) {
-            final var pu = addPackageUser(packageName, userId);
-            return pu != null ? pu.mPackageState : null;
+            return addPackageUser(packageName, userId);
         }
 
         public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1313,30 +1308,22 @@
             return (pkgs.length == 0) ? null : pkgs[0];
         }
 
+        @Nullable
         @Override
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @NonNull String targetOverlayableName, int userId)
                 throws IOException {
-            final var psu = getRawPackageStateForUser(packageName, userId);
-            final var pkg = (psu == null || psu.mPackageState == null)
-                    ? null : psu.mPackageState.getAndroidPackage();
+            var packageState = getPackageStateForUser(packageName, userId);
+            var pkg = packageState == null ? null : packageState.getAndroidPackage();
             if (pkg == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) {
-                return null;
-            }
-
             ApkAssets apkAssets = null;
             try {
                 apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
                         ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
-                if (psu.mDefinesOverlayable == null) {
-                    psu.mDefinesOverlayable = apkAssets.definesOverlayable();
-                }
-                return Boolean.FALSE.equals(psu.mDefinesOverlayable)
-                        ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
+                return apkAssets.getOverlayableInfo(targetOverlayableName);
             } finally {
                 if (apkAssets != null) {
                     try {
@@ -1350,29 +1337,24 @@
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws IOException {
-            final var psu = getRawPackageStateForUser(targetPackageName, userId);
-            var pkg = (psu == null || psu.mPackageState == null)
-                    ? null : psu.mPackageState.getAndroidPackage();
+            var packageState = getPackageStateForUser(targetPackageName, userId);
+            var pkg = packageState == null ? null : packageState.getAndroidPackage();
             if (pkg == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            if (psu.mDefinesOverlayable == null) {
-                ApkAssets apkAssets = null;
-                try {
-                    apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
-                            ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
-                    psu.mDefinesOverlayable = apkAssets.definesOverlayable();
-                } finally {
-                    if (apkAssets != null) {
-                        try {
-                            apkAssets.close();
-                        } catch (Throwable ignored) {
-                        }
+            ApkAssets apkAssets = null;
+            try {
+                apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
+                return apkAssets.definesOverlayable();
+            } finally {
+                if (apkAssets != null) {
+                    try {
+                        apkAssets.close();
+                    } catch (Throwable ignored) {
                     }
                 }
             }
-            return psu.mDefinesOverlayable;
         }
 
         @Override
@@ -1563,7 +1545,8 @@
                 final OverlayPaths frameworkOverlays =
                         mImpl.getEnabledOverlayPaths("android", userId, false);
                 for (final String targetPackageName : targetPackageNames) {
-                    final var list = new OverlayPaths.Builder(frameworkOverlays);
+                    final OverlayPaths.Builder list = new OverlayPaths.Builder();
+                    list.addAll(frameworkOverlays);
                     if (!"android".equals(targetPackageName)) {
                         list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
                     }
@@ -1575,21 +1558,17 @@
             final HashSet<String> invalidPackages = new HashSet<>();
             pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
 
-            if (DEBUG || !invalidPackages.isEmpty()) {
-                for (final String targetPackageName : targetPackageNames) {
-                    if (DEBUG) {
-                        Slog.d(TAG,
-                                "-> Updating overlay: target=" + targetPackageName + " overlays=["
-                                        + pendingChanges.get(targetPackageName)
-                                        + "] userId=" + userId);
-                    }
+            for (final String targetPackageName : targetPackageNames) {
+                if (DEBUG) {
+                    Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+                            + pendingChanges.get(targetPackageName)
+                            + "] userId=" + userId);
+                }
 
-                    if (invalidPackages.contains(targetPackageName)) {
-                        Slog.e(TAG, TextUtils.formatSimple(
-                                "Failed to change enabled overlays for %s user %d",
-                                targetPackageName,
-                                userId));
-                    }
+                if (invalidPackages.contains(targetPackageName)) {
+                    Slog.e(TAG, TextUtils.formatSimple(
+                            "Failed to change enabled overlays for %s user %d", targetPackageName,
+                            userId));
                 }
             }
             return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c1b6ccc..972c78d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,20 +772,24 @@
 
     OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
             final int userId, boolean includeImmutableOverlays) {
-        final var paths = new OverlayPaths.Builder();
-        mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
+        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+                userId);
+        final OverlayPaths.Builder paths = new OverlayPaths.Builder();
+        final int n = overlays.size();
+        for (int i = 0; i < n; i++) {
+            final OverlayInfo oi = overlays.get(i);
             if (!oi.isEnabled()) {
-                return;
+                continue;
             }
             if (!includeImmutableOverlays && !oi.isMutable) {
-                return;
+                continue;
             }
             if (oi.isFabricated()) {
                 paths.addNonApkPath(oi.baseCodePath);
             } else {
                 paths.addApkPath(oi.baseCodePath);
             }
-        });
+        }
         return paths.build();
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b8b49f3e..eae614a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,7 +47,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -183,23 +182,6 @@
         return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
     }
 
-    void forEachMatching(int userId, String overlayName, String targetPackageName,
-            @NonNull Consumer<OverlayInfo> consumer) {
-        for (int i = 0, n = mItems.size(); i < n; i++) {
-            final SettingsItem item = mItems.get(i);
-            if (item.getUserId() != userId) {
-                continue;
-            }
-            if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
-                continue;
-            }
-            if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
-                continue;
-            }
-            consumer.accept(item.getOverlayInfo());
-        }
-    }
-
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
         final List<SettingsItem> items = selectWhereUser(userId);
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 0be8e6e..ac89fecc 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3438,26 +3438,33 @@
                                 }
                             }
                         } else {
-                            if (allowSetMutation) {
-                                Slog.i(TAG,
-                                        "Result set changed, dropping preferred activity "
-                                                + "for " + intent + " type "
-                                                + resolvedType);
-                                if (DEBUG_PREFERRED) {
-                                    Slog.v(TAG,
-                                            "Removing preferred activity since set changed "
-                                                    + pa.mPref.mComponent);
+                            final boolean isHomeActivity = ACTION_MAIN.equals(intent.getAction())
+                                    && intent.hasCategory(CATEGORY_HOME);
+                            if (!Flags.improveHomeAppBehavior() || !isHomeActivity) {
+                                // Don't reset the preferred activity just for the home intent, we
+                                // should respect the default home app even though there any new
+                                // home activity is enabled.
+                                if (allowSetMutation) {
+                                    Slog.i(TAG,
+                                            "Result set changed, dropping preferred activity "
+                                                    + "for " + intent + " type "
+                                                    + resolvedType);
+                                    if (DEBUG_PREFERRED) {
+                                        Slog.v(TAG,
+                                                "Removing preferred activity since set changed "
+                                                        + pa.mPref.mComponent);
+                                    }
+                                    pir.removeFilter(pa);
+                                    // Re-add the filter as a "last chosen" entry (!always)
+                                    PreferredActivity lastChosen = new PreferredActivity(
+                                            pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
+                                            false);
+                                    pir.addFilter(this, lastChosen);
+                                    result.mChanged = true;
                                 }
-                                pir.removeFilter(pa);
-                                // Re-add the filter as a "last chosen" entry (!always)
-                                PreferredActivity lastChosen = new PreferredActivity(
-                                        pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
-                                        false);
-                                pir.addFilter(this, lastChosen);
-                                result.mChanged = true;
+                                result.mPreferredResolveInfo = null;
+                                return result;
                             }
-                            result.mPreferredResolveInfo = null;
-                            return result;
                         }
                     }
 
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index f1dc284..b9aa28e 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -49,6 +49,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -268,7 +269,8 @@
     private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) {
         final int callingUserId = mInjector.getCallingUserId();
         final int[] enabledProfileIds =
-                mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+                mInjector.getUserManager().getProfileIdsExcludingHidden(
+                        callingUserId, /* enabled= */ true);
         if (enabledProfileIds.length < 2) {
             return false;
         }
@@ -350,7 +352,8 @@
             String packageName, @UserIdInt int userId) {
         return mInjector.withCleanCallingIdentity(() -> {
             final int[] enabledProfileIds =
-                    mInjector.getUserManager().getEnabledProfileIds(userId);
+                    mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+                            true);
 
             List<UserHandle> targetProfiles = new ArrayList<>();
             for (final int profileId : enabledProfileIds) {
@@ -466,7 +469,8 @@
             return;
         }
         final int[] profileIds =
-                mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+                mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+                        false);
         for (int profileId : profileIds) {
             if (!isPackageInstalled(packageName, profileId)) {
                 continue;
@@ -632,7 +636,8 @@
     private boolean canUserAttemptToConfigureInteractAcrossProfiles(
             String packageName, @UserIdInt int userId) {
         final int[] profileIds =
-                mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+                mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+                        false);
         if (profileIds.length < 2) {
             return false;
         }
@@ -676,7 +681,8 @@
     private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) {
         return mInjector.withCleanCallingIdentity(() -> {
             final int[] profileIds =
-                    mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+                    mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+                            false);
             for (int profileId : profileIds) {
                 if (profileId != userId && isPackageInstalled(packageName, profileId)) {
                     return true;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 28f3d59..33f481c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2204,6 +2204,9 @@
                 File appMetadataFile = new File(ps.getPath(), APP_METADATA_FILE_NAME);
                 if (appMetadataFile.exists()) {
                     ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+                    if (Flags.aslInApkAppMetadataSource()) {
+                        ps.setAppMetadataSource(PackageManager.APP_METADATA_SOURCE_INSTALLER);
+                    }
                 } else {
                     ps.setAppMetadataFilePath(null);
                 }
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ebaf516..4fb0c22 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -42,6 +42,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
@@ -155,6 +156,9 @@
     @NonNull
     private final ArrayList<String> mWarnings = new ArrayList<>();
 
+    @Nullable
+    private DomainSet mPreVerifiedDomains;
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -172,6 +176,7 @@
         mIsInstallInherit = params.mIsInherit;
         mSessionId = params.mSessionId;
         mRequireUserAction = params.mRequireUserAction;
+        mPreVerifiedDomains = params.mPreVerifiedDomains;
     }
 
     // Install existing package as user
@@ -875,6 +880,11 @@
         }
     }
 
+    @Nullable
+    public DomainSet getPreVerifiedDomains() {
+        return mPreVerifiedDomains;
+    }
+
     public void addWarning(@NonNull String warning) {
         mWarnings.add(warning);
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index e970d2c..4cbd3ad 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -40,6 +40,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
 import android.os.Environment;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -98,6 +99,8 @@
     final int mSessionId;
     final int mRequireUserAction;
     final boolean mApplicationEnabledSettingPersistent;
+    @Nullable
+    final DomainSet mPreVerifiedDomains;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,12 +133,13 @@
         mSessionId = -1;
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
         mApplicationEnabledSettingPersistent = false;
+        mPreVerifiedDomains = null;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
             UserHandle user, SigningDetails signingDetails, int installerUid,
-            PackageLite packageLite, PackageManagerService pm) {
+            PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) {
         mPm = pm;
         mUser = user;
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -163,6 +167,7 @@
         mSessionId = sessionId;
         mRequireUserAction = sessionParams.requireUserAction;
         mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
+        mPreVerifiedDomains = preVerifiedDomains;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43328fc..984a629 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1676,6 +1676,8 @@
         private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
             Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
             appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+            appMarketIntent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
             return buildIntentSenderForUser(appMarketIntent, user);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 474b590..dc97e5f 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.START_ABORTED;
 import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
 import static android.app.ActivityManager.START_PERMISSION_DENIED;
-import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
@@ -29,6 +28,7 @@
 import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
 import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
 import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
+import static android.content.pm.PackageManager.DELETE_ALL_USERS;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
@@ -183,62 +183,76 @@
         return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
     }
 
+    @VisibleForTesting
     void requestArchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
             @NonNull UserHandle userHandle) {
+        requestArchive(packageName, callerPackageName, /*flags=*/ 0, intentSender, userHandle);
+    }
+
+    void requestArchive(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            int flags,
+            @NonNull IntentSender intentSender,
+            @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
         Objects.requireNonNull(intentSender);
         Objects.requireNonNull(userHandle);
 
         Computer snapshot = mPm.snapshotComputer();
-        int userId = userHandle.getIdentifier();
+        int binderUserId = userHandle.getIdentifier();
         int binderUid = Binder.getCallingUid();
         int binderPid = Binder.getCallingPid();
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
-            verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
+            verifyCaller(snapshot.getPackageUid(callerPackageName, 0, binderUserId), binderUid);
         }
-        snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
-                "archiveApp");
+
+        final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0;
+        final int[] users = deleteAllUsers ? mPm.mInjector.getUserManagerInternal().getUserIds()
+                : new int[]{binderUserId};
+        for (int userId : users) {
+            snapshot.enforceCrossUserPermission(binderUid, userId,
+                    /*requireFullPermission=*/ true, /*checkShell=*/ true,
+                    "archiveApp");
+        }
         verifyUninstallPermissions();
 
-        CompletableFuture<ArchiveState> archiveStateFuture;
+        CompletableFuture<Void>[] archiveStateStored = new CompletableFuture[users.length];
         try {
-            archiveStateFuture = createArchiveState(packageName, userId);
+            for (int i = 0, size = users.length; i < size; ++i) {
+                archiveStateStored[i] = createAndStoreArchiveState(packageName, users[i]);
+            }
         } catch (PackageManager.NameNotFoundException e) {
             Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
                     packageName, e.getMessage()));
             throw new ParcelableException(e);
         }
 
-        archiveStateFuture
-                .thenAccept(
-                        archiveState -> {
-                            // TODO(b/282952870) Should be reverted if uninstall fails/cancels
-                            try {
-                                storeArchiveState(packageName, archiveState, userId);
-                            } catch (PackageManager.NameNotFoundException e) {
-                                sendFailureStatus(intentSender, packageName, e.getMessage());
-                                return;
-                            }
+        final int deleteFlags = DELETE_ARCHIVE | DELETE_KEEP_DATA
+                | (deleteAllUsers ? DELETE_ALL_USERS : 0);
 
-                            mPm.mInstallerService.uninstall(
-                                    new VersionedPackage(packageName,
-                                            PackageManager.VERSION_CODE_HIGHEST),
-                                    callerPackageName,
-                                    DELETE_ARCHIVE | DELETE_KEEP_DATA,
-                                    intentSender,
-                                    userId,
-                                    binderUid,
-                                    binderPid);
-                        })
-                .exceptionally(
-                        e -> {
-                            sendFailureStatus(intentSender, packageName, e.getMessage());
-                            return null;
-                        });
+        CompletableFuture.allOf(archiveStateStored).thenAccept(ignored ->
+                mPm.mInstallerService.uninstall(
+                        new VersionedPackage(packageName,
+                                PackageManager.VERSION_CODE_HIGHEST),
+                        callerPackageName,
+                        deleteFlags,
+                        intentSender,
+                        binderUserId,
+                        binderUid,
+                        binderPid)
+        ).exceptionally(
+                e -> {
+                    Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
+                            packageName, e.getMessage()));
+                    sendFailureStatus(intentSender, packageName, e.getMessage());
+                    return null;
+                }
+        );
     }
 
     /**
@@ -286,10 +300,16 @@
             Slog.e(TAG, TextUtils.formatSimple(
                     "Unexpected error occurred while unarchiving package %s: %s.", packageName,
                     t.getLocalizedMessage()));
-            return START_ABORTED;
         }
 
-        return START_SUCCESS;
+        // We return STATUS_ABORTED because:
+        // 1. Archived App is not actually present during activity start. Hence the unarchival
+        // start should be treated as an error code.
+        // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user
+        // experience.
+        // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like
+        // aborting activity options, animations etc in the Windows Manager.
+        return START_ABORTED;
     }
 
     /**
@@ -379,7 +399,7 @@
     }
 
     /** Creates archived state for the package and user. */
-    private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
+    private CompletableFuture<Void> createAndStoreArchiveState(String packageName, int userId)
             throws PackageManager.NameNotFoundException {
         Computer snapshot = mPm.snapshotComputer();
         PackageStateInternal ps = getPackageState(packageName, snapshot,
@@ -394,17 +414,18 @@
 
         List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
                 userId);
-        final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
+        final CompletableFuture<Void> archiveStateStored = new CompletableFuture<>();
         mPm.mHandler.post(() -> {
             try {
-                archiveState.complete(
-                        createArchiveStateInternal(packageName, userId, mainActivities,
-                                installerInfo.loadLabel(mContext.getPackageManager()).toString()));
-            } catch (IOException e) {
-                archiveState.completeExceptionally(e);
+                var archiveState = createArchiveStateInternal(packageName, userId, mainActivities,
+                        installerInfo.loadLabel(mContext.getPackageManager()).toString());
+                storeArchiveState(packageName, archiveState, userId);
+                archiveStateStored.complete(null);
+            } catch (IOException | PackageManager.NameNotFoundException e) {
+                archiveStateStored.completeExceptionally(e);
             }
         });
-        return archiveState;
+        return archiveStateStored;
     }
 
     @Nullable
@@ -798,6 +819,7 @@
      * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
      * launcher activities, only one of the icons is returned arbitrarily.
      */
+    @Nullable
     public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
             String callingPackageName) {
         Objects.requireNonNull(packageName);
@@ -823,7 +845,7 @@
         // In the rare case the archived app defined more than two launcher activities, we choose
         // the first one arbitrarily.
         Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
-        if (getAppOpsManager().checkOp(
+        if (icon != null && getAppOpsManager().checkOp(
                 AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
                 == MODE_ALLOWED) {
             icon = includeCloudOverlay(icon);
@@ -879,6 +901,7 @@
         return bitmap;
     }
 
+    @Nullable
     Bitmap includeCloudOverlay(Bitmap bitmap) {
         Drawable cloudDrawable =
                 mContext.getResources()
@@ -899,7 +922,9 @@
         final int iconSize = mContext.getSystemService(
                 ActivityManager.class).getLauncherLargeIconSize();
         Bitmap appIconWithCloudOverlay = drawableToBitmap(layerDrawable, iconSize);
-        bitmap.recycle();
+        if (bitmap != null) {
+            bitmap.recycle();
+        }
         return appIconWithCloudOverlay;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
index d40a715..4b98e34 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -19,6 +19,7 @@
 import android.content.pm.PackageInstaller.PreapprovalDetails;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.verify.domain.DomainSet;
 
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -76,6 +77,7 @@
     private final boolean mSessionFailed;
     private final int mSessionErrorCode;
     private final String mSessionErrorMessage;
+    private final String mPreVerifiedDomains;
 
     PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
             String originalInstallerPackageName, InstallSource installSource, int installerUid,
@@ -86,7 +88,7 @@
             String finalMessage, SessionParams params, int parentSessionId,
             int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
             boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
-            PreapprovalDetails preapprovalDetails) {
+            PreapprovalDetails preapprovalDetails, DomainSet preVerifiedDomains) {
         this.sessionId = sessionId;
         this.userId = userId;
         this.mOriginalInstallerUid = originalInstallerUid;
@@ -128,6 +130,11 @@
         } else {
             this.mPreapprovalDetails = null;
         }
+        if (preVerifiedDomains != null) {
+            this.mPreVerifiedDomains = String.join(",", preVerifiedDomains.getDomains());
+        } else {
+            this.mPreVerifiedDomains = null;
+        }
     }
 
     void dump(IndentingPrintWriter pw) {
@@ -170,6 +177,7 @@
         pw.printPair("mSessionErrorCode", mSessionErrorCode);
         pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
         pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+        pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
         pw.println();
 
         pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c6d448d..6e4f199 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1026,7 +1026,7 @@
                 mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
                 userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                 null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
-                false, false, false, PackageManager.INSTALL_UNKNOWN, "");
+                false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
@@ -1672,9 +1672,11 @@
     public void requestArchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
+            int flags,
             @NonNull IntentSender intentSender,
             @NonNull UserHandle userHandle) {
-        mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle);
+        mPackageArchiver.requestArchive(packageName, callerPackageName, flags, intentSender,
+                userHandle);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 27c3dad..c860b5a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -113,6 +113,7 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
@@ -241,6 +242,8 @@
             "whitelisted-restricted-permission";
     private static final String TAG_AUTO_REVOKE_PERMISSIONS_MODE =
             "auto-revoke-permissions-mode";
+
+    static final String TAG_PRE_VERIFIED_DOMAINS = "preVerifiedDomains";
     private static final String ATTR_SESSION_ID = "sessionId";
     private static final String ATTR_USER_ID = "userId";
     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
@@ -298,6 +301,7 @@
     private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
     private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT =
             "applicationEnabledSettingPersistent";
+    private static final String ATTR_DOMAIN = "domain";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -365,6 +369,25 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     private static final long THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT = 240618202L;
 
+    /**
+     * Configurable maximum number of pre-verified domains allowed to be added to the session.
+     * Flag type: {@code long}
+     * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+     */
+    private static final String PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT =
+            "pre_verified_domains_count_limit";
+    /**
+     * Configurable maximum string length of each pre-verified domain.
+     * Flag type: {@code long}
+     * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+     */
+    private static final String PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT =
+            "pre_verified_domain_length_limit";
+    /** Default max number of pre-verified domains */
+    private static final long DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT = 1000;
+    /** Default max string length of each pre-verified domain */
+    private static final long DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT = 256;
+
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
 
@@ -506,6 +529,9 @@
     @GuardedBy("mLock")
     private int mUserActionRequirement;
 
+    @GuardedBy("mLock")
+    private DomainSet mPreVerifiedDomains;
+
     static class FileEntry {
         private final int mIndex;
         private final InstallationFile mFile;
@@ -936,6 +962,21 @@
                 getInstallSource().mInstallerPackageName, mInstallerUid);
     }
 
+    private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
+        final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+        if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
+            return false;
+        }
+        String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
+        if (emergencyInstaller == null || !ArrayUtils.contains(
+                snapshot.getPackagesForUid(mInstallerUid),
+                emergencyInstaller)) {
+            return false;
+        }
+        return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
+                mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+    }
+
     private static final int USER_ACTION_NOT_NEEDED = 0;
     private static final int USER_ACTION_REQUIRED = 1;
     private static final int USER_ACTION_PENDING_APK_PARSING = 2;
@@ -1020,6 +1061,8 @@
         final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName,
                 getInstallerPackageName());
         final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
+        final boolean isEmergencyInstall =
+                isEmergencyInstallerEnabled(packageName, snapshot);
         final boolean isPermissionGranted = isInstallPermissionGranted
                 || (isUpdatePermissionGranted && isUpdate)
                 || (isSelfUpdatePermissionGranted && isSelfUpdate)
@@ -1036,7 +1079,7 @@
         // Device owners and affiliated profile owners are allowed to silently install packages, so
         // the permission check is waived if the installer is the device owner.
         final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
-                || isInstallerDeviceOwnerOrAffiliatedProfileOwner();
+                || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
 
         if (noUserActionNecessary) {
             return userActionNotTypicallyNeededResponse;
@@ -1089,7 +1132,7 @@
             boolean prepared, boolean committed, boolean destroyed, boolean sealed,
             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
             boolean isFailed, boolean isApplied, int sessionErrorCode,
-            String sessionErrorMessage) {
+            String sessionErrorMessage, DomainSet preVerifiedDomains) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -1151,6 +1194,7 @@
         mSessionErrorMessage =
                 sessionErrorMessage != null ? sessionErrorMessage : "";
         mStagedSession = params.isStaged ? new StagedSession() : null;
+        mPreVerifiedDomains = preVerifiedDomains;
 
         if (isDataLoaderInstallation()) {
             if (isApexSession()) {
@@ -1198,7 +1242,7 @@
                     mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
                     mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
                     mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
-                    mSessionErrorMessage, mPreapprovalDetails);
+                    mSessionErrorMessage, mPreapprovalDetails, mPreVerifiedDomains);
         }
     }
 
@@ -3135,7 +3179,7 @@
 
         synchronized (mLock) {
             return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
-                    user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
+                    user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm);
         }
     }
 
@@ -5024,6 +5068,82 @@
         return (params.installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
     }
 
+    @Override
+    public void setPreVerifiedDomains(@NonNull DomainSet preVerifiedDomains) {
+        // First check permissions
+        final boolean exemptFromPermissionChecks =
+                (mInstallerUid == Process.ROOT_UID) || (mInstallerUid == Process.SHELL_UID);
+        if (!exemptFromPermissionChecks) {
+            final Computer snapshot = mPm.snapshotComputer();
+            if (PackageManager.PERMISSION_GRANTED != snapshot.checkUidPermission(
+                    Manifest.permission.ACCESS_INSTANT_APPS, mInstallerUid)) {
+                throw new SecurityException("You need android.permission.ACCESS_INSTANT_APPS "
+                        + "permission to set pre-verified domains.");
+            }
+            ComponentName instantAppInstallerComponent = snapshot.getInstantAppInstallerComponent();
+            if (instantAppInstallerComponent == null) {
+                // Shouldn't happen
+                throw new IllegalStateException("Instant app installer is not available. "
+                        + "Only the instant app installer can call this API.");
+            }
+            if (!instantAppInstallerComponent.getPackageName().equals(getInstallerPackageName())) {
+                throw new SecurityException("Only the instant app installer can call this API.");
+            }
+        }
+        // Then check size limits
+        final long preVerifiedDomainsCountLimit = getPreVerifiedDomainsCountLimit();
+        if (preVerifiedDomains.getDomains().size() > preVerifiedDomainsCountLimit) {
+            throw new IllegalArgumentException(
+                    "The number of pre-verified domains have exceeded the maximum of "
+                            + preVerifiedDomainsCountLimit);
+        }
+        final long preVerifiedDomainLengthLimit = getPreVerifiedDomainLengthLimit();
+        for (String domain : preVerifiedDomains.getDomains()) {
+            if (domain.length() > preVerifiedDomainLengthLimit) {
+                throw new IllegalArgumentException(
+                        "Pre-verified domain: [" + domain + " ] exceeds maximum length allowed: "
+                                + preVerifiedDomainLengthLimit);
+            }
+        }
+        // Okay to proceed
+        synchronized (mLock) {
+            mPreVerifiedDomains = preVerifiedDomains;
+        }
+    }
+
+    private static long getPreVerifiedDomainsCountLimit() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                    PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT,
+                    DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private static long getPreVerifiedDomainLengthLimit() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                    PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT,
+                    DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    @Nullable
+    public DomainSet getPreVerifiedDomains() {
+        assertCallerIsOwnerOrRoot();
+        synchronized (mLock) {
+            assertPreparedAndNotCommittedOrDestroyedLocked("getPreVerifiedDomains");
+            return mPreVerifiedDomains;
+        }
+    }
+
+
     void setSessionReady() {
         synchronized (mLock) {
             // Do not allow destroyed/failed session to change state
@@ -5250,6 +5370,9 @@
         pw.printPair("mSessionErrorCode", mSessionErrorCode);
         pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
         pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+        if (mPreVerifiedDomains != null) {
+            pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
+        }
         pw.println();
 
         pw.decreaseIndent();
@@ -5546,7 +5669,13 @@
                 writeByteArrayAttribute(out, ATTR_SIGNATURE, signature);
                 out.endTag(null, TAG_SESSION_CHECKSUM_SIGNATURE);
             }
-
+            if (mPreVerifiedDomains != null) {
+                for (String domain : mPreVerifiedDomains.getDomains()) {
+                    out.startTag(null, TAG_PRE_VERIFIED_DOMAINS);
+                    writeStringAttribute(out, ATTR_DOMAIN, domain);
+                    out.endTag(null, TAG_PRE_VERIFIED_DOMAINS);
+                }
+            }
         }
 
         out.endTag(null, TAG_SESSION);
@@ -5673,6 +5802,7 @@
         List<InstallationFile> files = new ArrayList<>();
         ArrayMap<String, List<Checksum>> checksums = new ArrayMap<>();
         ArrayMap<String, byte[]> signatures = new ArrayMap<>();
+        ArraySet<String> preVerifiedDomainSet = new ArraySet<>();
         int outerDepth = in.getDepth();
         int type;
         while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -5726,6 +5856,9 @@
                     final byte[] signature = readByteArrayAttribute(in, ATTR_SIGNATURE);
                     signatures.put(fileName1, signature);
                     break;
+                case TAG_PRE_VERIFIED_DOMAINS:
+                    preVerifiedDomainSet.add(readStringAttribute(in, ATTR_DOMAIN));
+                    break;
             }
         }
 
@@ -5769,6 +5902,9 @@
             }
         }
 
+        DomainSet preVerifiedDomains =
+                preVerifiedDomainSet.isEmpty() ? null : new DomainSet(preVerifiedDomainSet);
+
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
                 installOriginatingPackageName, installerPackageName, installPackageUid,
                 updateOwnerPackageName, installerAttributionTag, params.packageSource);
@@ -5777,6 +5913,6 @@
                 installerUid, installSource, params, createdMillis, committedMillis, stageDir,
                 stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
                 childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
-                sessionErrorCode, sessionErrorMessage);
+                sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b705e84..b8960da 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2559,12 +2559,20 @@
                         PackageSetting pkgSetting = mSettings.getPackageLPr(pkgName);
                         if (pkgSetting != null) {
                             pkgSetting.setAppMetadataFilePath(path);
+                            if (Flags.aslInApkAppMetadataSource()) {
+                                pkgSetting.setAppMetadataSource(
+                                        PackageManager.APP_METADATA_SOURCE_SYSTEM_IMAGE);
+                            }
                         } else {
                             Slog.w(TAG, "Cannot set app metadata file for nonexistent package "
                                     + pkgName);
                         }
                     } else {
                         disabledPkgSetting.setAppMetadataFilePath(path);
+                        if (Flags.aslInApkAppMetadataSource()) {
+                            disabledPkgSetting.setAppMetadataSource(
+                                    PackageManager.APP_METADATA_SOURCE_SYSTEM_IMAGE);
+                        }
                     }
                 }
             }
@@ -5231,6 +5239,21 @@
             return null;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.GET_APP_METADATA)
+        @Override
+        public int getAppMetadataSource(String packageName, int userId) {
+            getAppMetadataSource_enforcePermission();
+            final int callingUid = Binder.getCallingUid();
+            final Computer snapshot = snapshotComputer();
+            final PackageStateInternal ps = snapshot.getPackageStateForInstalledAndFiltered(
+                    packageName, callingUid, userId);
+            if (ps == null) {
+                throw new ParcelableException(
+                        new PackageManager.NameNotFoundException(packageName));
+            }
+            return ps.getAppMetadataSource();
+        }
+
         @Override
         public String getPermissionControllerPackageName() {
             final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5c9c8c6..e329f09 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -107,6 +107,7 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
@@ -270,6 +271,10 @@
                     return runGetInstallLocation();
                 case "install-add-session":
                     return runInstallAddSession();
+                case "install-set-pre-verified-domains":
+                    return runInstallSetPreVerifiedDomains();
+                case "install-get-pre-verified-domains":
+                    return runInstallGetPreVerifiedDomains();
                 case "move-package":
                     return runMovePackage();
                 case "move-primary-storage":
@@ -1808,6 +1813,41 @@
                 true /*logSuccess*/);
     }
 
+    private int runInstallSetPreVerifiedDomains() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final int sessionId = Integer.parseInt(getNextArg());
+        final String preVerifiedDomainsStr = getNextArg();
+        final String[] preVerifiedDomains = preVerifiedDomainsStr.split(",");
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+            session.setPreVerifiedDomains(new ArraySet<>(preVerifiedDomains));
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+        return 0;
+    }
+
+    private int runInstallGetPreVerifiedDomains() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final int sessionId = Integer.parseInt(getNextArg());
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+            Set<String> preVerifiedDomains = session.getPreVerifiedDomains();
+            if (preVerifiedDomains.isEmpty()) {
+                pw.println("The session doesn't have any pre-verified domains specified.");
+            } else {
+                pw.println(String.join(",", preVerifiedDomains));
+            }
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+        return 0;
+    }
+
     private int runInstallRemove() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
 
@@ -4651,6 +4691,7 @@
 
     private int runArchive() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
+        int flags = 0;
         int userId = UserHandle.USER_ALL;
 
         String opt;
@@ -4678,13 +4719,16 @@
             return 1;
         }
 
+        if (userId == UserHandle.USER_ALL) {
+            flags |= PackageManager.DELETE_ALL_USERS;
+        }
         final int translatedUserId =
                 translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
         final LocalIntentReceiver receiver = new LocalIntentReceiver();
 
         try {
             mInterface.getPackageInstaller().requestArchive(packageName,
-                    /* callerPackageName= */ "", receiver.getIntentSender(),
+                    /* callerPackageName= */ "", flags, receiver.getIntentSender(),
                     new UserHandle(translatedUserId));
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
@@ -4930,6 +4974,13 @@
         pw.println("  install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs");
         pw.println("    Add one or more session IDs to a multi-package session.");
         pw.println("");
+        pw.println("  install-set-pre-verified-domains SESSION_ID PRE_VERIFIED_DOMAIN... ");
+        pw.println("    Specify a comma separated list of pre-verified domains for a session.");
+        pw.println("");
+        pw.println("  install-get-pre-verified-domains SESSION_ID");
+        pw.println("    List all the pre-verified domains that are specified in a session.");
+        pw.println("    The result list is comma separated.");
+        pw.println("");
         pw.println("  install-commit SESSION_ID");
         pw.println("    Commit the given active install session, installing the app.");
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index f474d32..12eb88e 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -224,6 +224,8 @@
     @Nullable
     private String mAppMetadataFilePath;
 
+    private int mAppMetadataSource = PackageManager.APP_METADATA_SOURCE_UNKNOWN;
+
     private int mTargetSdkVersion;
 
     @Nullable
@@ -716,6 +718,7 @@
         categoryOverride = other.categoryOverride;
         mDomainSetId = other.mDomainSetId;
         mAppMetadataFilePath = other.mAppMetadataFilePath;
+        mAppMetadataSource = other.mAppMetadataSource;
         mTargetSdkVersion = other.mTargetSdkVersion;
         mRestrictUpdateHash = other.mRestrictUpdateHash == null
                 ? null : other.mRestrictUpdateHash.clone();
@@ -1378,6 +1381,15 @@
         return this;
     }
 
+    /**
+     * @param source the source of the app metadata that is currently associated with
+     */
+    public PackageSetting setAppMetadataSource(int source) {
+        mAppMetadataSource = source;
+        onChanged();
+        return this;
+    }
+
     @NonNull
     @Override
     public long getVersionCode() {
@@ -1818,6 +1830,11 @@
     }
 
     @DataClass.Generated.Member
+    public int getAppMetadataSource() {
+        return mAppMetadataSource;
+    }
+
+    @DataClass.Generated.Member
     public int getTargetSdkVersion() {
         return mTargetSdkVersion;
     }
@@ -1828,10 +1845,10 @@
     }
 
     @DataClass.Generated(
-            time = 1702666890740L,
+            time = 1706698406378L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable boolean[] usesSdkLibrariesOptional\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate @android.annotation.Nullable java.util.LinkedHashSet<java.io.File> mOldPaths\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  void setArchiveState(com.android.server.pm.pkg.ArchiveState,int)\n  boolean getInstalled(int)\n  boolean isArchived(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\npublic  com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting addOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting removeOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override boolean[] getUsesSdkLibrariesOptional()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesOptional(boolean[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable boolean[] usesSdkLibrariesOptional\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate @android.annotation.Nullable java.util.LinkedHashSet<java.io.File> mOldPaths\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mAppMetadataSource\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  void setArchiveState(com.android.server.pm.pkg.ArchiveState,int)\n  boolean getInstalled(int)\n  boolean isArchived(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOnAnyOtherUser(int[],int)\n  boolean hasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\npublic  com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  void restoreComponentSettings(int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting addOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting removeOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setAppMetadataSource(int)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override boolean[] getUsesSdkLibrariesOptional()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesOptional(boolean[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index c7ee649..5575f52 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -924,6 +924,7 @@
             ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
             ret.setMimeGroups(p.getMimeGroups());
             ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
+            ret.setAppMetadataSource(p.getAppMetadataSource());
             ret.getPkgState().setUpdatedSystemApp(false);
             ret.setTargetSdkVersion(p.getTargetSdkVersion());
             ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
@@ -3122,6 +3123,9 @@
                     pkg.getAppMetadataFilePath());
         }
 
+        serializer.attributeInt(null, "appMetadataSource",
+                pkg.getAppMetadataSource());
+
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
                 pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
 
@@ -3226,6 +3230,9 @@
         if (pkg.getAppMetadataFilePath() != null) {
             serializer.attribute(null, "appMetadataFilePath", pkg.getAppMetadataFilePath());
         }
+        serializer.attributeInt(null, "appMetadataSource",
+                pkg.getAppMetadataSource());
+
 
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
                 pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
@@ -3942,6 +3949,8 @@
         }
 
         ps.setAppMetadataFilePath(parser.getAttributeValue(null, "appMetadataFilePath"));
+        ps.setAppMetadataSource(parser.getAttributeInt(null,
+                "appMetadataSource", PackageManager.APP_METADATA_SOURCE_UNKNOWN));
 
         int outerDepth = parser.getDepth();
         int type;
@@ -4021,6 +4030,7 @@
         long loadingCompletedTime = 0;
         UUID domainSetId;
         String appMetadataFilePath = null;
+        int appMetadataSource = PackageManager.APP_METADATA_SOURCE_UNKNOWN;
         int targetSdkVersion = 0;
         byte[] restrictUpdateHash = null;
         boolean isScannedAsStoppedSystemApp = false;
@@ -4066,6 +4076,9 @@
             categoryHint = parser.getAttributeInt(null, "categoryHint",
                     ApplicationInfo.CATEGORY_UNDEFINED);
             appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
+            appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
+                    PackageManager.APP_METADATA_SOURCE_UNKNOWN);
+
             isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
                 "scannedAsStoppedSystemApp", false);
 
@@ -4214,6 +4227,7 @@
                     .setLoadingProgress(loadingProgress)
                     .setLoadingCompletedTime(loadingCompletedTime)
                     .setAppMetadataFilePath(appMetadataFilePath)
+                    .setAppMetadataSource(appMetadataSource)
                     .setTargetSdkVersion(targetSdkVersion)
                     .setRestrictUpdateHash(restrictUpdateHash)
                     .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
@@ -5030,6 +5044,10 @@
                 pw.print(prefix); pw.print("  updatableSystem=false");
                 pw.println();
             }
+            if (pkg.getEmergencyInstaller() != null) {
+                pw.print(prefix); pw.print("  emergencyInstaller=");
+                pw.println(pkg.getEmergencyInstaller());
+            }
             if (pkg.hasPreserveLegacyExternalStorage()) {
                 pw.print(prefix); pw.print("  hasPreserveLegacyExternalStorage=true");
                 pw.println();
@@ -5223,6 +5241,8 @@
         }
         pw.print(prefix); pw.print("  appMetadataFilePath=");
         pw.println(ps.getAppMetadataFilePath());
+        pw.print(prefix); pw.print("  appMetadataSource=");
+        pw.println(ps.getAppMetadataSource());
         if (ps.getVolumeUuid() != null) {
             pw.print(prefix); pw.print("  volumeUuid=");
                     pw.println(ps.getVolumeUuid());
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index e9c6aab..b35f9c2 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -209,9 +209,8 @@
         if (ret == null) {
             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
             mLaunchers.put(key, ret);
-        } else {
-            ret.attemptToRestoreIfNeededAndSave();
         }
+        ret.attemptToRestoreIfNeededAndSave();
         return ret;
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c0596bb..796edde 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1385,7 +1385,7 @@
 
     @Override
     public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
-        return getProfileIds(userId, null, enabledOnly);
+        return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ false);
     }
 
     // TODO(b/142482943): Probably @Override and make this accessible in UserManager.
@@ -1397,14 +1397,14 @@
      * If enabledOnly, only returns users that are not {@link UserInfo#FLAG_DISABLED}.
      */
     public int[] getProfileIds(@UserIdInt int userId, @Nullable String userType,
-            boolean enabledOnly) {
+            boolean enabledOnly, boolean excludeHidden) {
         if (userId != UserHandle.getCallingUserId()) {
             checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
         }
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mUsersLock) {
-                return getProfileIdsLU(userId, userType, enabledOnly).toArray();
+                return getProfileIdsLU(userId, userType, enabledOnly, excludeHidden).toArray();
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1415,7 +1415,8 @@
     @GuardedBy("mUsersLock")
     private List<UserInfo> getProfilesLU(@UserIdInt int userId, @Nullable String userType,
             boolean enabledOnly, boolean fullInfo) {
-        IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly);
+        IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly, /* excludeHidden */
+                false);
         ArrayList<UserInfo> users = new ArrayList<>(profileIds.size());
         for (int i = 0; i < profileIds.size(); i++) {
             int profileId = profileIds.get(i);
@@ -1440,7 +1441,7 @@
      */
     @GuardedBy("mUsersLock")
     private IntArray getProfileIdsLU(@UserIdInt int userId, @Nullable String userType,
-            boolean enabledOnly) {
+            boolean enabledOnly, boolean excludeHidden) {
         UserInfo user = getUserInfoLU(userId);
         IntArray result = new IntArray(mUsers.size());
         if (user == null) {
@@ -1465,11 +1466,36 @@
             if (userType != null && !userType.equals(profile.userType)) {
                 continue;
             }
+            if (excludeHidden && isProfileHidden(userId)) {
+                continue;
+            }
             result.add(profile.id);
         }
         return result;
     }
 
+    /*
+     * Returns all the users that are in the same profile group as userId excluding those with
+     * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+     * the user itself.
+     */
+    // TODO (b/323011770): Add a permission check to make an exception for App stores if we end
+    //  up supporting Private Space on COPE devices
+    @Override
+    public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabledOnly) {
+        return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ true);
+    }
+
+    private boolean isProfileHidden(int userId) {
+        UserProperties userProperties = getUserPropertiesCopy(userId);
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableHidingProfiles()) {
+            return userProperties.getProfileApiVisibility()
+                    == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
+        }
+        return false;
+    }
+
     @Override
     public int getCredentialOwnerProfile(@UserIdInt int userId) {
         checkManageUsersPermission("get the credential owner");
@@ -3630,7 +3656,8 @@
                 return 0;
             }
 
-            final int userTypeCount = getProfileIds(userId, userType, false).length;
+            final int userTypeCount = getProfileIds(userId, userType, false, /* excludeHidden */
+                    false).length;
             final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
             final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
                     - profilesRemovedCount;
@@ -5931,7 +5958,8 @@
             }
             userData = mUsers.get(userId);
             isProfile = userData.info.isProfile();
-            profileIds = isProfile ? null : getProfileIdsLU(userId, null, false);
+            profileIds = isProfile ? null : getProfileIdsLU(userId, null, false, /* excludeHidden */
+                    false);
         }
 
         if (!isProfile) {
@@ -7458,7 +7486,8 @@
         @Override
         public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
             synchronized (mUsersLock) {
-                return getProfileIdsLU(userId, null /* userType */, enabledOnly).toArray();
+                return getProfileIdsLU(userId, null /* userType */, enabledOnly, /* excludeHidden */
+                        false).toArray();
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 23d0230..b720304 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -304,7 +304,8 @@
                         UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
                 .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
                 .setCrossProfileContentSharingStrategy(
-                        UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT);
+                        UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
+                .setItemsRestrictedOnHomeScreen(true);
         if (android.multiuser.Flags.supportHidingProfiles()) {
             userPropertiesBuilder.setProfileApiVisibility(
                     UserProperties.PROFILE_API_VISIBILITY_HIDDEN);
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index cc26c9b..9159851 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -538,9 +538,10 @@
             } else {
                 if (fileInfo.mUserId != userId) {
                     // This should be impossible: private app files are always user-specific and
-                    // can't be accessed from different users.
-                    throw new IllegalArgumentException("Cannot change userId for '" + path
-                            + "' from " + fileInfo.mUserId + " to " + userId);
+                    // can't be accessed from different users. But it does very occasionally happen
+                    // (b/323665257). Ignore such cases - we shouldn't record data from a different
+                    // user.
+                    return false;
                 }
                 // Changing file type (i.e. loading the same file in different ways is possible if
                 // unlikely. We allow it but ignore it.
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 6ed2d31..a9e5a54 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -831,7 +831,7 @@
             retProcs.put(proc.getName(),
                     new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
                             proc.getGwpAsanMode(), proc.getMemtagMode(),
-                            proc.getNativeHeapZeroInitialized()));
+                            proc.getNativeHeapZeroInitialized(), proc.isUseEmbeddedDex()));
         }
         return retProcs;
     }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index f7603b5..85ea83a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -116,4 +116,9 @@
 
     @Nullable
     Set<File> getOldPaths();
+
+    /**
+     * @return the source of the app metadata that is currently associated with the given package.
+     */
+    int getAppMetadataSource();
 }
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 7754944..07cc775 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -17,11 +17,14 @@
 package com.android.server.policy;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 
 import com.android.server.devicestate.DeviceStatePolicy;
 import com.android.server.devicestate.DeviceStateProvider;
 
+import java.io.PrintWriter;
+
 /**
  * Default empty implementation of {@link DeviceStatePolicy}.
  *
@@ -43,4 +46,9 @@
     public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
         onComplete.run();
     }
+
+    @Override
+    public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        mProvider.dump(writer, args);
+    }
 }
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 3644054..afcf5a0 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -58,6 +58,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -503,6 +504,24 @@
         // Do nothing.
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        writer.println("DeviceStateProviderImpl");
+
+        synchronized (mLock) {
+            writer.println("  mLastReportedState = " + mLastReportedState);
+            writer.println("  mPowerSaveModeEnabled = " + mPowerSaveModeEnabled);
+            writer.println("  mThermalStatus = " + mThermalStatus);
+            writer.println("  mIsLidOpen = " + mIsLidOpen);
+            writer.println("  Sensor values:");
+
+            for (Sensor sensor : mLatestSensorEvent.keySet()) {
+                SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
+                writer.println("   - " + toSensorValueString(sensor, sensorEvent));
+            }
+        }
+    }
+
     /**
      * Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
      * switch open state matches {@link #mIsLidOpen}.
@@ -669,14 +688,16 @@
         Slog.i(TAG, "Sensor values:");
         for (Sensor sensor : mLatestSensorEvent.keySet()) {
             SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
-            if (sensorEvent != null) {
-                Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(sensorEvent.values));
-            } else {
-                Slog.i(TAG, sensor.getName() + ": null");
-            }
+            Slog.i(TAG, toSensorValueString(sensor, sensorEvent));
         }
     }
 
+    private String toSensorValueString(Sensor sensor, @Nullable SensorEvent event) {
+        String sensorString = sensor == null ? "null" : sensor.getName();
+        String eventValues = event == null ? "null" : Arrays.toString(event.values);
+        return sensorString + " : " + eventValues;
+    }
+
     /**
      * Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
      * {@code null} if the file could not be successfully parsed.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51790b8..775a361 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4708,6 +4708,9 @@
             case KeyEvent.KEYCODE_BACK: {
                 logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
                 if (down) {
+                    // There may have other embedded activities on the same Task. Try to move the
+                    // focus before processing the back event.
+                    mWindowManagerInternal.moveFocusToTopEmbeddedWindowIfNeeded();
                     mBackKeyHandled = false;
                 } else {
                     if (!hasLongPressOnBackBehavior()) {
@@ -5016,6 +5019,8 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+                Slog.i(TAG, "Stylus buttons event: " + keyCode + " received. Should handle event? "
+                        + mStylusButtonsEnabled);
                 if (mStylusButtonsEnabled) {
                     sendSystemKeyToStatusBarAsync(event);
                 }
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index 906da2f..b05a421 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -59,6 +59,10 @@
         final Set<ComponentName> enabledServices =
                 AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
         ComponentName componentName = getTalkbackComponent();
+        if (componentName == null) {
+            return false;
+        }
+
         boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
 
         if (isTalkBackShortcutGestureEnabled()) {
@@ -67,7 +71,7 @@
                     isTalkbackAlreadyEnabled);
 
             // log stem triple press telemetry if it's a talkback enabled event.
-            if (componentName != null && isTalkbackAlreadyEnabled) {
+            if (isTalkbackAlreadyEnabled) {
                 logStemTriplePressAccessibilityTelemetry(componentName);
             }
         }
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 6f75439..35717af 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -691,16 +691,26 @@
                     TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
                             workDuration.getActualTotalDurationNanos()));
             }
-            if (workDuration.getActualCpuDurationNanos() <= 0) {
+            if (workDuration.getActualCpuDurationNanos() < 0) {
                 throw new IllegalArgumentException(
-                    TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
+                    TextUtils.formatSimple(
+                        "Actual CPU duration (%d) should be greater than or equal to 0",
                             workDuration.getActualCpuDurationNanos()));
             }
             if (workDuration.getActualGpuDurationNanos() < 0) {
                 throw new IllegalArgumentException(
-                    TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
+                    TextUtils.formatSimple(
+                        "Actual GPU duration (%d) should greater than or equal to 0",
                             workDuration.getActualGpuDurationNanos()));
             }
+            if (workDuration.getActualCpuDurationNanos()
+                    + workDuration.getActualGpuDurationNanos() <= 0) {
+                throw new IllegalArgumentException(
+                    TextUtils.formatSimple(
+                        "The actual CPU duration (%d) and the actual GPU duration (%d)"
+                        + " should not both be 0", workDuration.getActualCpuDurationNanos(),
+                        workDuration.getActualGpuDurationNanos()));
+            }
         }
 
         private void onProcStateChanged(boolean updateAllowed) {
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 6546646..b2e01c5 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -21,3 +21,10 @@
     bug: "311793616"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "streamlined_connectivity_battery_stats"
+    namespace: "backstage_power"
+    description: "Feature flag for streamlined connectivity battery stats"
+    bug: "323970018"
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 089a886..e0a8226 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2095,17 +2095,17 @@
         }
 
         @Override
-        public void startPlayback(IBinder sessionToken, int userId) {
+        public void resumePlayback(IBinder sessionToken, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
-                    userId, "stopPlayback");
+                    userId, "resumePlayback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     try {
-                        getSessionLocked(sessionToken, callingUid, resolvedUserId).startPlayback();
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId).resumePlayback();
                     } catch (RemoteException | SessionNotFoundException e) {
-                        Slog.e(TAG, "error in startPlayback()", e);
+                        Slog.e(TAG, "error in resumePlayback()", e);
                     }
                 }
             } finally {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8549957..37f3825 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -347,7 +347,7 @@
         for (Rect crop : relativeCropHints) {
             Rect originalRect = new Rect(crop);
             originalRect.scale(wallpaper.mSampleSize);
-            originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+            originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
             result.add(originalRect);
         }
         return result;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8c27bb8..118985a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -145,7 +145,6 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -613,8 +612,8 @@
     }
 
     private final Context mContext;
-    private final AtomicBoolean mIsInitialBinding = new AtomicBoolean(true);
-    private final ServiceThread mHandlerThread;
+    private boolean mInitialUserSwitch = true;
+    private ServiceThread mHandlerThread;
     private final WindowManagerInternal mWindowManagerInternal;
     private final PackageManagerInternal mPackageManagerInternal;
     private final IPackageManager mIPackageManager;
@@ -1474,12 +1473,6 @@
     public WallpaperManagerService(Context context) {
         if (DEBUG) Slog.v(TAG, "WallpaperService startup");
         mContext = context;
-        if (Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) {
-            mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/);
-            mHandlerThread.start();
-        } else {
-            mHandlerThread = null;
-        }
         mShuttingDown = false;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
@@ -1803,6 +1796,7 @@
                     switchWallpaper(lockWallpaper, null);
                 }
                 switchWallpaper(systemWallpaper, reply);
+                mInitialUserSwitch = false;
             }
 
             // Offload color extraction to another thread since switchUser will be called
@@ -3326,11 +3320,8 @@
                     com.android.internal.R.bool.config_wallpaperTopApp)) {
                 bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
             }
-            Handler handler = Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()
-                    && !mIsInitialBinding.compareAndSet(true, false)
-                    ? mHandlerThread.getThreadHandler() : mContext.getMainThreadHandler();
-            boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags, handler,
-                    new UserHandle(serviceUserId));
+            boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
+                    getHandlerForBindingWallpaperLocked(), new UserHandle(serviceUserId));
             if (!bindSuccess) {
                 String msg = "Unable to bind service: " + componentName;
                 if (fromUser) {
@@ -3358,6 +3349,20 @@
         return true;
     }
 
+    private Handler getHandlerForBindingWallpaperLocked() {
+        if (!Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) {
+            return mContext.getMainThreadHandler();
+        }
+        if (mInitialUserSwitch) {
+            return mContext.getMainThreadHandler();
+        }
+        if (mHandlerThread == null) {
+            mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/);
+            mHandlerThread.start();
+        }
+        return mHandlerThread.getThreadHandler();
+    }
+
     // Updates tracking of the currently bound wallpapers.
     private void updateCurrentWallpapers(WallpaperData newWallpaper) {
         if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 1128d0c..2e0546e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1319,33 +1319,9 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                if (r == null) return;
-                final TransitionController controller = r.mTransitionController;
-                if (!controller.isShellTransitionsEnabled()) {
+                if (r != null) {
                     r.setShowWhenLocked(showWhenLocked);
-                    return;
                 }
-                if (controller.isCollecting()
-                        && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
-                    // Keyguard isn't locked, so this can be done as part of the collecting
-                    // transition.
-                    r.setShowWhenLocked(showWhenLocked);
-                    return;
-                }
-                final Transition transition = new Transition(
-                        showWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
-                        0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
-                r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
-                    transition.collect(r);
-                    r.setShowWhenLocked(showWhenLocked);
-                    if (transition.isNoOp()) {
-                        transition.abort();
-                        return;
-                    }
-                    controller.requestStartTransition(transition, null /* trigger */,
-                            null /* remoteTransition */, null /* displayChange */);
-                    transition.setReady(r, true);
-                });
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -1358,34 +1334,9 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                if (r == null) return;
-                final TransitionController controller = r.mTransitionController;
-                // If shell transitions is not enabled just set it directly.
-                if (!controller.isShellTransitionsEnabled()) {
+                if (r != null) {
                     r.setInheritShowWhenLocked(inheritShowWhenLocked);
-                    return;
                 }
-                if (controller.isCollecting()
-                        && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
-                    // Keyguard isn't locked, so this can be done as part of the collecting
-                    // transition.
-                    r.setInheritShowWhenLocked(inheritShowWhenLocked);
-                    return;
-                }
-                final Transition transition = new Transition(
-                        inheritShowWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
-                        0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
-                r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
-                    transition.collect(r);
-                    r.setInheritShowWhenLocked(inheritShowWhenLocked);
-                    if (transition.isNoOp()) {
-                        transition.abort();
-                        return;
-                    }
-                    controller.requestStartTransition(transition, null /* trigger */,
-                            null /* remoteTransition */, null /* displayChange */);
-                    transition.setReady(r, true);
-                });
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b1d04c9..7b59759 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -122,6 +122,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
+import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
@@ -386,6 +387,7 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.WindowManagerService.H;
 import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -960,6 +962,9 @@
     // without security checks
     final Binder shareableActivityToken = new Binder();
 
+    // Token for accessing the initial caller who started the activity.
+    final IBinder initialCallerInfoAccessToken = new Binder();
+
     // Tracking cookie for the launch of this activity and it's task.
     IBinder mLaunchCookie;
 
@@ -986,6 +991,9 @@
     // Whether the ActivityEmbedding is enabled on the app.
     private final boolean mAppActivityEmbeddingSplitsEnabled;
 
+    // Whether the Activity allows state sharing in untrusted embedding
+    private final boolean mAllowUntrustedEmbeddingStateSharing;
+
     // Records whether client has overridden the WindowAnimation_(Open/Close)(Enter/Exit)Animation.
     private CustomAppTransition mCustomOpenTransition;
     private CustomAppTransition mCustomCloseTransition;
@@ -2223,6 +2231,7 @@
             // No such property name.
         }
         mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled;
+        mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
 
         mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
                 .isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
@@ -3078,6 +3087,32 @@
         return parent != null && parent.isEmbedded();
     }
 
+    /**
+     * Returns {@code true} if the system is allowed to share this activity's state with the host
+     * app when this activity is embedded in untrusted mode.
+     */
+    boolean isUntrustedEmbeddingStateSharingAllowed() {
+        if (!Flags.untrustedEmbeddingStateSharing()) {
+            return false;
+        }
+        return mAllowUntrustedEmbeddingStateSharing;
+    }
+
+    private boolean getAllowUntrustedEmbeddingStateSharingProperty() {
+        if (!Flags.untrustedEmbeddingStateSharing()) {
+            return false;
+        }
+        try {
+            return mAtmService.mContext.getPackageManager()
+                    .getProperty(PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING,
+                            mActivityComponent)
+                    .getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            // No such property name.
+            return false;
+        }
+    }
+
     @Override
     @Nullable
     TaskDisplayArea getDisplayArea() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6f52b8..85580ac 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1034,20 +1034,19 @@
         }
 
         if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+            // We couldn't find the specific class specified in the Intent.
+            err = ActivityManager.START_CLASS_NOT_FOUND;
+
             if (isArchivingEnabled()) {
                 PackageArchiver packageArchiver = mService
                         .getPackageManagerInternalLocked()
                         .getPackageArchiver();
                 if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
-                    return packageArchiver
+                    err = packageArchiver
                             .requestUnarchiveOnActivityStart(
                                     intent, callingPackage, mRequest.userId, realCallingUid);
                 }
             }
-
-            // We couldn't find the specific class specified in the Intent.
-            // Also the end of the line.
-            err = ActivityManager.START_CLASS_NOT_FOUND;
         }
 
         if (err == ActivityManager.START_SUCCESS && sourceRecord != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3397a3d..0def5a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3505,8 +3505,9 @@
     }
 
     @Override
-    public boolean isAssistDataAllowedOnCurrentActivity() {
+    public boolean isAssistDataAllowed() {
         int userId;
+        boolean hasRestrictedWindow;
         synchronized (mGlobalLock) {
             final Task focusedRootTask = getTopDisplayFocusedRootTask();
             if (focusedRootTask == null || focusedRootTask.isActivityTypeAssistant()) {
@@ -3518,8 +3519,17 @@
                 return false;
             }
             userId = activity.mUserId;
+            DisplayContent displayContent = activity.getDisplayContent();
+            if (displayContent == null) {
+                return false;
+            }
+            hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+                return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+                        getUserManager().getProfileType(windowState.mShowUserId));
+            }, true /* traverseTopToBottom */);
         }
-        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
+        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
+                && !hasRestrictedWindow;
     }
 
     private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ec0e3e7..49df396 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -930,7 +930,8 @@
                         proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
                         results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward,
                         proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
-                        r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken);
+                        r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken,
+                        r.initialCallerInfoAccessToken);
 
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
@@ -943,7 +944,10 @@
 
                 // Schedule transaction.
                 mService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
-                        proc.getThread(), launchActivityItem, lifecycleItem);
+                        proc.getThread(), launchActivityItem, lifecycleItem,
+                        // Immediately dispatch the transaction, so that if it fails, the server can
+                        // restart the process and retry now.
+                        true /* shouldDispatchImmediately */);
 
                 if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
                     // If the seq is increased, there should be something changed (e.g. registered
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 13f6a5f..c2dfa21 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.View.FOCUS_FORWARD;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
@@ -61,7 +60,6 @@
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.utils.InsetUtils;
-import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -165,21 +163,12 @@
                 return null;
             }
 
-            // Move focus to the adjacent embedded window if it is higher than this window
-            final TaskFragment taskFragment = window.getTaskFragment();
-            final TaskFragment adjacentTaskFragment =
-                    taskFragment != null ? taskFragment.getAdjacentTaskFragment() : null;
-            if (adjacentTaskFragment != null && taskFragment.isEmbedded()
-                    && Flags.embeddedActivityBackNavFlag()) {
-                final WindowContainer parent = taskFragment.getParent();
-                if (parent.mChildren.indexOf(taskFragment) < parent.mChildren.indexOf(
-                        adjacentTaskFragment)) {
-                    mWindowManagerService.moveFocusToAdjacentWindow(window, FOCUS_FORWARD);
-                    window = wmService.getFocusedWindowLocked();
-                    if (window == null) {
-                        Slog.e(TAG, "Adjacent window is null, returning null.");
-                        return null;
-                    }
+            // Move focus to the top embedded window if possible
+            if (mWindowManagerService.moveFocusToTopEmbeddedWindow(window)) {
+                window = wmService.getFocusedWindowLocked();
+                if (window == null) {
+                    Slog.e(TAG, "New focused window is null, returning null.");
+                    return null;
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index c7df83a..5b4fb3e 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -64,6 +64,10 @@
         final IApplicationThread client = transaction.getClient();
         try {
             transaction.schedule();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to deliver transaction for " + client
+                            + "\ntransaction=" + transaction);
+            throw e;
         } finally {
             if (!(client instanceof Binder)) {
                 // If client is not an instance of Binder - it's a remote call and at this point it
@@ -106,7 +110,8 @@
             final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
             clientTransaction.addTransactionItem(transactionItem);
 
-            onClientTransactionItemScheduled(clientTransaction);
+            onClientTransactionItemScheduled(clientTransaction,
+                    false /* shouldDispatchImmediately */);
         } else {
             // TODO(b/260873529): cleanup after launch.
             final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -119,14 +124,30 @@
         }
     }
 
+    void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
+            @NonNull ClientTransactionItem transactionItem,
+            @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+        scheduleTransactionAndLifecycleItems(client, transactionItem, lifecycleItem,
+                false /* shouldDispatchImmediately */);
+    }
+
     /**
      * Schedules a single transaction item with a lifecycle request, delivery to client application.
+     *
+     * @param shouldDispatchImmediately whether or not to dispatch the transaction immediately. This
+     *                                  should only be {@code true} when it is important to know the
+     *                                  result of dispatching immediately. For example, when cold
+     *                                  launches an app, the server needs to know if the transaction
+     *                                  is dispatched successfully, and may restart the process if
+     *                                  not.
+     *
      * @throws RemoteException
      * @see ClientTransactionItem
      */
     void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem,
-            @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+            @NonNull ActivityLifecycleItem lifecycleItem,
+            boolean shouldDispatchImmediately) throws RemoteException {
         // The behavior is different depending on the flag.
         // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
         // dispatch all pending transactions at once.
@@ -135,7 +156,7 @@
             clientTransaction.addTransactionItem(transactionItem);
             clientTransaction.addTransactionItem(lifecycleItem);
 
-            onClientTransactionItemScheduled(clientTransaction);
+            onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
         } else {
             // TODO(b/260873529): cleanup after launch.
             final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -157,7 +178,8 @@
             try {
                 scheduleTransaction(transaction);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to deliver transaction for " + transaction.getClient());
+                Slog.e(TAG, "Failed to deliver pending transaction", e);
+                // TODO(b/323801078): apply cleanup for individual transaction item if needed.
             }
         }
         mPendingTransactions.clear();
@@ -194,8 +216,9 @@
 
     /** Must only be called with WM lock. */
     private void onClientTransactionItemScheduled(
-            @NonNull ClientTransaction clientTransaction) throws RemoteException {
-        if (shouldDispatchPendingTransactionsImmediately()) {
+            @NonNull ClientTransaction clientTransaction,
+            boolean shouldDispatchImmediately) throws RemoteException {
+        if (shouldDispatchImmediately || shouldDispatchPendingTransactionsImmediately()) {
             // Dispatch the pending transaction immediately.
             mPendingTransactions.remove(clientTransaction.getClient().asBinder());
             scheduleTransaction(clientTransaction);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82dbf8d..e743172 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1429,14 +1429,6 @@
         return mTokenMap.get(binder);
     }
 
-    ActivityRecord getActivityRecord(IBinder binder) {
-        final WindowToken token = getWindowToken(binder);
-        if (token == null) {
-            return null;
-        }
-        return token.asActivityRecord();
-    }
-
     void addWindowToken(IBinder binder, WindowToken token) {
         final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
         if (dc != null) {
@@ -2250,7 +2242,6 @@
             }
         }
 
-        mWmService.mDisplayManagerInternal.performTraversal(transaction);
         if (shellTransitions) {
             // Before setDisplayProjection is applied by the start transaction of transition,
             // set the transform hint to avoid using surface in old rotation.
@@ -5159,6 +5150,15 @@
 
     /** @return the orientation of the display when it's rotation is ROTATION_0. */
     int getNaturalOrientation() {
+        return mBaseDisplayWidth <= mBaseDisplayHeight
+                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+    }
+
+    /**
+     * Returns the orientation which is used for app's Configuration (excluding decor insets) when
+     * the display rotation is ROTATION_0.
+     */
+    int getNaturalConfigurationOrientation() {
         final Configuration config = getConfiguration();
         if (config.windowConfiguration.getDisplayRotation() == ROTATION_0) {
             return config.orientation;
@@ -6985,7 +6985,7 @@
 
         @Override
         public void onAppTransitionFinishedLocked(IBinder token) {
-            final ActivityRecord r = getActivityRecord(token);
+            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
             // Ignore the animating recents so the fixed rotation transform won't be switched twice
             // by finishing the recents animation and moving it to top. That also avoids flickering
             // due to wait for previous activity to be paused if it supports PiP that ignores the
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 34d7651..4204670 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -132,7 +132,7 @@
     void show(SurfaceControl.Transaction t, WindowContainer w) {
         t.show(mInputSurface);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
+        t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1 + w.getChildCount());
     }
 
     void show(SurfaceControl.Transaction t, int layer) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d9dda4a..cd96806 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -178,6 +178,7 @@
             mWindowContainer.cancelAnimation();
             mWindowContainer.getInsetsSourceProviders().remove(mSource.getId());
             mSeamlessRotating = false;
+            mHasPendingPosition = false;
         }
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
                 windowContainer, WindowInsets.Type.toString(mSource.getType()));
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index f6aad4c..e66321a 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -23,6 +23,7 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.InputConfig;
 import android.view.GestureDetector;
@@ -258,11 +259,12 @@
         private final GestureDetector mDoubleTapDetector;
         private final DoubleTapListener mDoubleTapListener;
 
-        TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService) {
-            super(inputChannel, UiThread.getHandler().getLooper());
+        TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService,
+                Handler uiHandler) {
+            super(inputChannel, uiHandler.getLooper());
             mDoubleTapListener = new DoubleTapListener(wmService);
-            mDoubleTapDetector = new GestureDetector(
-                    wmService.mContext, mDoubleTapListener, UiThread.getHandler());
+            mDoubleTapDetector = new GestureDetector(wmService.mContext, mDoubleTapListener,
+                    uiHandler);
         }
 
         @Override
@@ -294,19 +296,21 @@
         }
     }
 
-    private final class InputInterceptor {
+    private final class InputInterceptor implements Runnable {
 
         private final InputChannel mClientChannel;
         private final InputWindowHandle mWindowHandle;
         private final InputEventReceiver mInputEventReceiver;
         private final WindowManagerService mWmService;
         private final IBinder mToken;
+        private final Handler mHandler;
 
         InputInterceptor(String namePrefix, WindowState win) {
             mWmService = win.mWmService;
+            mHandler = UiThread.getHandler();
             final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
             mClientChannel = mWmService.mInputManager.createInputChannel(name);
-            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService);
+            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService, mHandler);
 
             mToken = mClientChannel.getToken();
 
@@ -335,11 +339,17 @@
             mWindowHandle.touchableRegion.translate(-frame.left, -frame.top);
         }
 
-        void dispose() {
-            mWmService.mInputManager.removeInputChannel(mToken);
+        @Override
+        public void run() {
             mInputEventReceiver.dispose();
             mClientChannel.dispose();
         }
+
+        void dispose() {
+            mWmService.mInputManager.removeInputChannel(mToken);
+            // Perform dispose on the same thread that dispatches input event
+            mHandler.post(this);
+        }
     }
 
     private class LetterboxSurface {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0e2d3d1..edf9da1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -58,6 +58,8 @@
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -927,8 +929,7 @@
     }
 
     void updateLetterboxSurface(WindowState winHint, Transaction t) {
-        final WindowState w = mActivityRecord.findMainWindow();
-        if (w != winHint && winHint != null && w != null) {
+        if (shouldNotLayoutLetterbox(winHint)) {
             return;
         }
         layoutLetterbox(winHint);
@@ -937,20 +938,11 @@
         }
     }
 
-    void layoutLetterbox(WindowState winHint) {
-        final WindowState w = mActivityRecord.findMainWindow();
-        if (w == null || winHint != null && w != winHint) {
+    void layoutLetterbox(WindowState w) {
+        if (shouldNotLayoutLetterbox(w)) {
             return;
         }
         updateRoundedCornersIfNeeded(w);
-        // If there is another main window that is not an application-starting window, we should
-        // update rounded corners for it as well, to avoid flickering rounded corners.
-        final WindowState nonStartingAppW = mActivityRecord.findMainWindow(
-                /* includeStartingApp= */ false);
-        if (nonStartingAppW != null && nonStartingAppW != w) {
-            updateRoundedCornersIfNeeded(nonStartingAppW);
-        }
-
         updateWallpaperForLetterbox(w);
         if (shouldShowLetterboxUi(w)) {
             if (mLetterbox == null) {
@@ -1023,6 +1015,18 @@
         return mActivityRecord.getSurfaceControl();
     }
 
+    private static boolean shouldNotLayoutLetterbox(WindowState w) {
+        if (w == null) {
+            return true;
+        }
+        final int type = w.mAttrs.type;
+        // Allow letterbox to be displayed early for base application or application starting
+        // windows even if it is not on the top z order to prevent flickering when the
+        // letterboxed window is brought to the top
+        return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING)
+                || w.mAnimatingExit;
+    }
+
     private boolean shouldLetterboxHaveRoundedCorners() {
         // TODO(b/214030873): remove once background is drawn for transparent activities
         // Letterbox shouldn't have rounded corners if the activity is transparent
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index cd70447..e06f215 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -17,6 +17,7 @@
 rgl@google.com
 yunfanc@google.com
 wilsonshih@google.com
+jiamingliu@google.com
 
 # Files related to background activity launches
 per-file Background*Start* = set noparent
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index eae9951..d08f043 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.internal.perfetto.protos.PerfettoTrace;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.tracing.perfetto.DataSourceParams;
 import android.tracing.perfetto.InitArguments;
 import android.tracing.perfetto.Producer;
@@ -57,6 +58,16 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logSentTransition");
+        try {
+            doLogSentTransition(transition, targets);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogSentTransition(
+            Transition transition, ArrayList<Transition.ChangeInfo> targets) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -91,6 +102,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logFinishedTransition");
+        try {
+            doLogFinishTransition(transition);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogFinishTransition(Transition transition) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -114,6 +134,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAbortedTransition");
+        try {
+            doLogAbortedTransition(transition);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogAbortedTransition(Transition transition) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -131,6 +160,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logRemovingStartingWindow");
+        try {
+            doLogRemovingStartingWindow(startingData);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    public void doLogRemovingStartingWindow(@NonNull StartingData startingData) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 587cc74..25646f1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -249,6 +249,8 @@
     /** Reference to default display so we can quickly look it up. */
     private DisplayContent mDefaultDisplay;
     private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+    private final SparseArray<SurfaceControl.Transaction> mDisplayTransactions =
+            new SparseArray<>();
 
     /** The current user */
     int mCurrentUser;
@@ -569,22 +571,6 @@
         }, true /* traverseTopToBottom */);
     }
 
-    /**
-     * Returns the app window token for the input binder if it exist in the system.
-     * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
-     * AppWindowToken represents an activity which can only exist on one display.
-     */
-    ActivityRecord getActivityRecord(IBinder binder) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final DisplayContent dc = mChildren.get(i);
-            final ActivityRecord activity = dc.getActivityRecord(binder);
-            if (activity != null) {
-                return activity;
-            }
-        }
-        return null;
-    }
-
     /** Returns the window token for the input binder if it exist in the system. */
     WindowToken getWindowToken(IBinder binder) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -991,11 +977,13 @@
         for (int j = 0; j < count; ++j) {
             final DisplayContent dc = mChildren.get(j);
             dc.applySurfaceChangesTransaction();
+            mDisplayTransactions.append(dc.mDisplayId, dc.getSyncTransaction());
         }
 
         // Give the display manager a chance to adjust properties like display rotation if it needs
         // to.
-        mWmService.mDisplayManagerInternal.performTraversal(t);
+        mWmService.mDisplayManagerInternal.performTraversal(t, mDisplayTransactions);
+        mDisplayTransactions.clear();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index bdb4588..967f415 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -36,8 +36,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
 
 public class ScreenRecordingCallbackController {
 
@@ -57,10 +56,10 @@
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+    private final ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<>();
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+    private final ArrayMap<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
 
     private final WindowManagerService mWms;
 
@@ -108,8 +107,8 @@
         }
 
         IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
-        IMediaProjectionManager mediaProjectionManager =
-                IMediaProjectionManager.Stub.asInterface(binder);
+        IMediaProjectionManager mediaProjectionManager = IMediaProjectionManager.Stub.asInterface(
+                binder);
 
         long identityToken = Binder.clearCallingIdentity();
         MediaProjectionInfo mediaProjectionInfo = null;
@@ -157,11 +156,14 @@
         synchronized (mWms.mGlobalLock) {
             IBinder binder = callback.asBinder();
             Callback callbackInfo = mCallbacks.remove(binder);
+            if (callbackInfo == null) {
+                return;
+            }
             binder.unlinkToDeath(callbackInfo, 0);
 
             boolean uidHasCallback = false;
-            for (Callback cb : mCallbacks.values()) {
-                if (cb.mUid == callbackInfo.mUid) {
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                if (mCallbacks.valueAt(i).mUid == callbackInfo.mUid) {
                     uidHasCallback = true;
                     break;
                 }
@@ -213,7 +215,9 @@
             return;
         }
 
-        dispatchCallbacks(Set.of(uid), processVisible);
+        ArraySet<Integer> uidSet = new ArraySet<>();
+        uidSet.add(uid);
+        dispatchCallbacks(uidSet, processVisible);
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
@@ -233,8 +237,8 @@
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private Set<Integer> getRecordedUids() {
-        Set<Integer> result = new ArraySet<>();
+    private ArraySet<Integer> getRecordedUids() {
+        ArraySet<Integer> result = new ArraySet<>();
         if (mRecordedWC == null) {
             return result;
         }
@@ -248,37 +252,43 @@
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+    private void dispatchCallbacks(ArraySet<Integer> uids, boolean visibleInScreenRecording) {
         if (uids.isEmpty()) {
             return;
         }
 
-        for (Integer uid : uids) {
-            mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+        for (int i = 0; i < uids.size(); i++) {
+            mLastInvokedStateByUid.put(uids.valueAt(i), visibleInScreenRecording);
         }
 
-        for (Callback callback : mCallbacks.values()) {
-            if (!uids.contains(callback.mUid)) {
-                continue;
-            }
-            try {
-                callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
-            } catch (RemoteException e) {
-                // Client has died. Cleanup is handled via DeathRecipient.
+        ArrayList<IScreenRecordingCallback> callbacks = new ArrayList<>();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            if (uids.contains(mCallbacks.valueAt(i).mUid)) {
+                callbacks.add(mCallbacks.valueAt(i).mCallback);
             }
         }
+
+        mWms.mH.post(() -> {
+            for (int i = 0; i < callbacks.size(); i++) {
+                try {
+                    callbacks.get(i).onScreenRecordingStateChanged(visibleInScreenRecording);
+                } catch (RemoteException e) {
+                    // Client has died. Cleanup is handled via DeathRecipient.
+                }
+            }
+        });
     }
 
     void dump(PrintWriter pw) {
         pw.format("ScreenRecordingCallbackController:\n");
         pw.format("  Registered callbacks:\n");
-        for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
-            pw.format("    callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            pw.format("    callback=%s uid=%s\n", mCallbacks.keyAt(i), mCallbacks.valueAt(i).mUid);
         }
         pw.format("  Last invoked states:\n");
-        for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
-            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
-                    entry.getValue());
+        for (int i = 0; i < mLastInvokedStateByUid.size(); i++) {
+            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", mLastInvokedStateByUid.keyAt(i),
+                    mLastInvokedStateByUid.valueAt(i));
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 083872a..95e6ca6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -1005,7 +1005,14 @@
     public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            return mService.moveFocusToAdjacentWindow(this, fromWindow, direction);
+            synchronized (mService.mGlobalLock) {
+                final WindowState win =
+                        mService.windowForClientLocked(this, fromWindow, false /* throwOnError */);
+                if (win == null) {
+                    return false;
+                }
+                return mService.moveFocusToAdjacentWindow(win, direction);
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f9ed83..5b51776 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5070,7 +5070,7 @@
             mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
 
             if (waitingActivity != null) {
-                mWmService.setWindowOpaqueLocked(waitingActivity.token, false);
+                waitingActivity.setMainWindowOpaque(false);
                 if (waitingActivity.attachedToProcess()) {
                     try {
                         waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 81fe453..8d054db 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -376,10 +376,15 @@
                         + " is not in a task belong to the organizer app.");
                 return null;
             }
-            if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
-                    || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
+            if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
                 Slog.d(TAG, "Reparent activity=" + activity.token
-                        + " is not allowed to be embedded in trusted mode.");
+                        + " is not allowed to be embedded.");
+                return null;
+            }
+            if (!task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)
+                    && !activity.isUntrustedEmbeddingStateSharingAllowed()) {
+                Slog.d(TAG, "Reparent activity=" + activity.token
+                        + " is not allowed to be shared with untrusted host.");
                 return null;
             }
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 27cc2d6..7fc61e1 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -66,7 +66,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.WeakHashMap;
-import java.util.function.Consumer;
 
 /**
  * Stores the TaskOrganizers associated with a given windowing mode and
@@ -102,11 +101,8 @@
      */
     private static class TaskOrganizerCallbacks {
         final ITaskOrganizer mTaskOrganizer;
-        final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
-        TaskOrganizerCallbacks(ITaskOrganizer taskOrg,
-                Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
-            mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
+        TaskOrganizerCallbacks(ITaskOrganizer taskOrg) {
             mTaskOrganizer = taskOrg;
         }
 
@@ -335,11 +331,7 @@
         private final int mUid;
 
         TaskOrganizerState(ITaskOrganizer organizer, int uid) {
-            final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
-                    mDeferTaskOrgCallbacksConsumer != null
-                            ? mDeferTaskOrgCallbacksConsumer
-                            : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
-            mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
+            mOrganizer = new TaskOrganizerCallbacks(organizer);
             mDeathRecipient = new DeathRecipient(organizer);
             mPendingEventsQueue = new TaskOrganizerPendingEventsQueue(this);
             try {
@@ -484,8 +476,6 @@
     // Set of organized tasks (by taskId) that dispatch back pressed to their organizers
     private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet<>();
 
-    private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
-
     TaskOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
         mGlobalLock = atm.mGlobalLock;
@@ -502,15 +492,6 @@
     }
 
     /**
-     * Specifies the consumer to run to defer the task org callbacks. Can be overridden while
-     * testing to allow the callbacks to be sent synchronously.
-     */
-    @VisibleForTesting
-    public void setDeferTaskOrgCallbacksConsumer(Consumer<Runnable> consumer) {
-        mDeferTaskOrgCallbacksConsumer = consumer;
-    }
-
-    /**
      * Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
      */
     @Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1e58306..2accf9a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2917,26 +2917,6 @@
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
     }
 
-    /**
-     * Get whether the transition, in its current state, is a no-op. This should be avoided. It is
-     * only here for legacy usages where we can't tell ahead-of-time whether something will
-     * generate a change.
-     */
-    boolean isNoOp() {
-        for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            // This is the same criteria as the rejection logic in calculateTargets
-            final WindowContainer<?> wc = mParticipants.valueAt(i);
-            if (!wc.isAttached()) continue;
-            // The level of transition target should be at least window token.
-            if (wc.asWindowState() != null) continue;
-            final ChangeInfo changeInfo = mChanges.get(wc);
-            // Reject no-ops
-            if (!changeInfo.hasChanged()) continue;
-            return false;
-        }
-        return true;
-    }
-
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index f8edc2b..debe794 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -88,7 +88,17 @@
 
     void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
         requireOverlaySurfaceControl();
-        mOverlays.add(p);
+
+        boolean hasExistingOverlay = false;
+        for (int i = mOverlays.size() - 1; i >= 0; i--) {
+            SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+            if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+                hasExistingOverlay = true;
+            }
+        }
+        if (!hasExistingOverlay) {
+            mOverlays.add(p);
+        }
 
         SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
         t.reparent(p.getSurfaceControl(), mSurfaceControl)
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 1688a1a..817901f 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -32,7 +32,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Size;
@@ -159,10 +158,6 @@
 
     private InputWindowHandle[] mLastWindowHandles;
 
-    private final Object mIgnoredWindowTokensLock = new Object();
-
-    private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
-
     private void startHandlerThreadIfNeeded() {
         synchronized (mHandlerThreadLock) {
             if (mHandler == null) {
@@ -173,18 +168,6 @@
         }
     }
 
-    void addIgnoredWindowTokens(IBinder token) {
-        synchronized (mIgnoredWindowTokensLock) {
-            mIgnoredWindowTokens.add(token);
-        }
-    }
-
-    void removeIgnoredWindowTokens(IBinder token) {
-        synchronized (mIgnoredWindowTokensLock) {
-            mIgnoredWindowTokens.remove(token);
-        }
-    }
-
     void registerListener(IBinder window, ITrustedPresentationListener listener,
             TrustedPresentationThresholds thresholds, int id) {
         startHandlerThreadIfNeeded();
@@ -271,12 +254,8 @@
 
         ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
                 new ArrayMap<>();
-        ArraySet<IBinder> ignoredWindowTokens;
-        synchronized (mIgnoredWindowTokensLock) {
-            ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
-        }
         for (var windowHandle : mLastWindowHandles) {
-            if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+            if (!windowHandle.canOccludePresentation) {
                 ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
                 continue;
             }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2d2857a..80894b2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1589,7 +1589,7 @@
         if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
             // NOSENSOR means the display's "natural" orientation, so return that.
             if (mDisplayContent != null) {
-                return mDisplayContent.getNaturalOrientation();
+                return mDisplayContent.getNaturalConfigurationOrientation();
             }
         } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
             // LOCKED means the activity's orientation remains unchanged, so return existing value.
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index d0b9a6e..ae4c3b9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -1047,4 +1047,9 @@
      * * {@link #addBlockScreenCaptureForApps(ArraySet)}
      */
     public abstract void clearBlockedApps();
+
+    /**
+     * Moves the current focus to the top activity window if the top activity is embedded.
+     */
+    public abstract boolean moveFocusToTopEmbeddedWindowIfNeeded();
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 426694d..57448cb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
 import android.Manifest;
 import android.Manifest.permission;
 import android.animation.ValueAnimator;
+import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -345,6 +346,7 @@
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
 import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -1073,7 +1075,7 @@
 
         @Override
         public void onAppTransitionFinishedLocked(IBinder token) {
-            final ActivityRecord atoken = mRoot.getActivityRecord(token);
+            final ActivityRecord atoken = ActivityRecord.forTokenLocked(token);
             if (atoken == null) {
                 return;
             }
@@ -3105,13 +3107,6 @@
         return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
     }
 
-    void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
-        final ActivityRecord wtoken = mRoot.getActivityRecord(token);
-        if (wtoken != null) {
-            wtoken.setMainWindowOpaque(isOpaque);
-        }
-    }
-
     boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
         return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
                 aspectRatio);
@@ -3292,7 +3287,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
+    @EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
     /**
      * @see android.app.KeyguardManager#exitKeyguardSecurely
      */
@@ -4520,7 +4515,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public SurfaceControl addShellRoot(int displayId, IWindow client,
             @WindowManager.ShellRootLayer int shellRootLayer) {
@@ -4539,7 +4534,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void setShellRootAccessibilityWindow(int displayId,
             @WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
@@ -4562,7 +4557,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void setDisplayWindowInsetsController(
             int displayId, IDisplayWindowInsetsController insetsController) {
@@ -4581,7 +4576,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void updateDisplayWindowRequestedVisibleTypes(
             int displayId, @InsetsType int requestedVisibleTypes) {
@@ -5841,7 +5836,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplaySize(int displayId, int width, int height) {
         setForcedDisplaySize_enforcePermission();
@@ -5859,7 +5854,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplayScalingMode(int displayId, int mode) {
         setForcedDisplayScalingMode_enforcePermission();
@@ -5947,7 +5942,7 @@
         return changed;
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void clearForcedDisplaySize(int displayId) {
         clearForcedDisplaySize_enforcePermission();
@@ -6010,7 +6005,7 @@
         return -1;
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
         setForcedDisplayDensityForUser_enforcePermission();
@@ -6036,7 +6031,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void clearForcedDisplayDensityForUser(int displayId, int userId) {
         clearForcedDisplayDensityForUser_enforcePermission();
@@ -6536,7 +6531,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
+    @EnforcePermission(android.Manifest.permission.STATUS_BAR)
     public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
         setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
 
@@ -6578,7 +6573,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    @EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
     @Override
     public Region getCurrentImeTouchRegion() {
         getCurrentImeTouchRegion_enforcePermission();
@@ -8468,12 +8463,13 @@
                 SurfaceControlViewHost.SurfacePackage overlay) {
             if (overlay == null) {
                 throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
-            } else if (overlay.getSurfaceControl() == null
-                    || !overlay.getSurfaceControl().isValid()) {
-                throw new IllegalArgumentException(
-                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
             }
             synchronized (mGlobalLock) {
+                if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                    throw new IllegalArgumentException(
+                            "Invalid overlay surfacecontrol passed in for task=" + taskId);
+                }
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
                     throw new IllegalArgumentException("no task with taskId" + taskId);
@@ -8634,6 +8630,24 @@
                 }
             }
         }
+
+        @Override
+        public boolean moveFocusToTopEmbeddedWindowIfNeeded() {
+            synchronized (mGlobalLock) {
+                final WindowState focusedWindow = getFocusedWindow();
+                if (focusedWindow == null) {
+                    return false;
+                }
+
+                if (moveFocusToTopEmbeddedWindow(focusedWindow)) {
+                    // Sync the input transactions to ensure the input focus updates as well.
+                    syncInputTransactions(false);
+                    return true;
+                }
+
+                return false;
+            }
+        }
     }
 
     private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
@@ -9167,18 +9181,48 @@
                 win.mClient);
     }
 
-    boolean moveFocusToAdjacentWindow(Session session, IWindow fromWindow,
-            @FocusDirection int direction) {
-        synchronized (mGlobalLock) {
-            final WindowState fromWin = windowForClientLocked(session, fromWindow, false);
-            if (fromWin == null || !fromWin.isFocused()) {
-                return false;
-            }
-            return moveFocusToAdjacentWindow(fromWin, direction);
+    /**
+     * Move focus to the top embedded window if possible.
+     */
+    boolean moveFocusToTopEmbeddedWindow(@NonNull WindowState focusedWindow) {
+        final TaskFragment taskFragment = focusedWindow.getTaskFragment();
+        if (taskFragment == null) {
+            // Skip if not an Activity window.
+            return false;
         }
+
+        if (!Flags.embeddedActivityBackNavFlag()) {
+            // Skip if flag is not enabled.
+            return false;
+        }
+
+        final ActivityRecord topActivity =
+                taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
+        if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
+            // Skip if the focused activity is already the top-most activity on the Task.
+            return false;
+        }
+
+        if (!topActivity.isEmbedded()) {
+            // Skip if the top activity is not embedded
+            return false;
+        }
+
+        final TaskFragment topTaskFragment = topActivity.getTaskFragment();
+        if (topTaskFragment.isIsolatedNav()
+                && taskFragment.getAdjacentTaskFragment() == topTaskFragment) {
+            // Skip if the top TaskFragment is adjacent to current focus and is set to isolated nav.
+            return false;
+        }
+
+        moveFocusToActivity(topActivity);
+        return !focusedWindow.isFocused();
     }
 
-    boolean moveFocusToAdjacentWindow(WindowState fromWin, @FocusDirection int direction) {
+    boolean moveFocusToAdjacentWindow(@NonNull WindowState fromWin, @FocusDirection int direction) {
+        if (!fromWin.isFocused()) {
+            return false;
+        }
         final TaskFragment fromFragment = fromWin.getTaskFragment();
         if (fromFragment == null) {
             return false;
@@ -9227,12 +9271,14 @@
         if (topRunningActivity == null) {
             return false;
         }
-        moveDisplayToTopInternal(topRunningActivity.getDisplayId());
-        handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
-        if (fromWin.isFocused()) {
-            return false;
-        }
-        return true;
+        moveFocusToActivity(topRunningActivity);
+        return !fromWin.isFocused();
+    }
+
+    @VisibleForTesting
+    void moveFocusToActivity(@NonNull ActivityRecord activity) {
+        moveDisplayToTopInternal(activity.getDisplayId());
+        handleTaskFocusChange(activity.getTask(), activity);
     }
 
     /** Return whether layer tracing is enabled */
@@ -9956,13 +10002,17 @@
         mTrustedPresentationListenerController.unregisterListener(listener, id);
     }
 
+    @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
     @Override
     public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+        registerScreenRecordingCallback_enforcePermission();
         return mScreenRecordingCallbackController.register(callback);
     }
 
+    @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
     @Override
     public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+        unregisterScreenRecordingCallback_enforcePermission();
         mScreenRecordingCallbackController.unregister(callback);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 24e50c5..68dade0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1148,10 +1148,6 @@
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
 
-        if (token.mRoundedCornerOverlay) {
-            mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
-                    getWindowToken());
-        }
     }
 
     @Override
@@ -1163,6 +1159,9 @@
         if (secureWindowState()) {
             getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
         }
+        // All apps should be considered as occluding when computing TrustedPresentation Thresholds.
+        final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow;
+        getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation);
     }
 
     void updateTrustedOverlay() {
@@ -2344,9 +2343,6 @@
         mSession.onWindowRemoved(this);
         mWmService.postWindowRemoveCleanupLocked(this);
 
-        mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
-                getWindowToken());
-
         consumeInsetsChange();
     }
 
@@ -3024,8 +3020,10 @@
             return false;
         }
         if (doAnimation) {
-            mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
-            if (!isAnimating(TRANSITION | PARENTS)) {
+            // If a hide animation is applied, then let onAnimationFinished
+            // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false
+            // to commit invisible immediately.
+            if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) {
                 doAnimation = false;
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 44cd23d..6428591 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -509,8 +509,10 @@
 
         // We don't apply animation for application main window here since this window type
         // should be controlled by ActivityRecord in general. Wallpaper is also excluded because
-        // WallpaperController should handle it.
-        if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
+        // WallpaperController should handle it. Also skip play enter animation for the window
+        // below starting window.
+        if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+                && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
             applyAnimationLocked(transit, true);
         }
 
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index cc08488..df7fb99 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -31,5 +31,8 @@
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
 per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
 
+# Memory
+per-file com_android_server_am_OomConnection.cpp = file:/MEMORY_OWNERS
+
 # Bug component : 158088 = per-file *AnrTimer*
 per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp
index e892d23..49a3ad3 100644
--- a/services/core/jni/com_android_server_am_OomConnection.cpp
+++ b/services/core/jni/com_android_server_am_OomConnection.cpp
@@ -22,13 +22,15 @@
 
 namespace android {
 
+using namespace ::android::bpf::memevents;
+
 // Used to cache the results of the JNI name lookup
 static struct {
     jclass clazz;
     jmethodID ctor;
 } sOomKillRecordInfo;
 
-static memevents::MemEventListener memevent_listener;
+static MemEventListener memevent_listener(MemEventClient::AMS);
 
 /**
  * Initialize listening and waiting for new out-of-memory (OOM) events to occur.
@@ -42,25 +44,20 @@
  * @throws java.lang.RuntimeException
  */
 static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject) {
-    const memevents::MemEvent oom_event = memevents::MemEvent::OOM_KILL;
-    if (!memevent_listener.registerEvent(oom_event)) {
+    if (!memevent_listener.registerEvent(MEM_EVENT_OOM_KILL)) {
         memevent_listener.deregisterAllEvents();
         jniThrowRuntimeException(env, "listener failed to register to OOM events");
         return nullptr;
     }
 
-    memevents::MemEvent event_received;
-    do {
-        event_received = memevent_listener.listen();
-        if (event_received == memevents::MemEvent::ERROR) {
-            memevent_listener.deregisterAllEvents();
-            jniThrowRuntimeException(env, "listener received error event");
-            return nullptr;
-        }
-    } while (event_received != oom_event);
+    if (!memevent_listener.listen()) {
+        memevent_listener.deregisterAllEvents();
+        jniThrowRuntimeException(env, "listener failed waiting for OOM event");
+        return nullptr;
+    }
 
-    std::vector<memevents::OomKill> oom_events;
-    if (!memevent_listener.getOomEvents(oom_events)) {
+    std::vector<mem_event_t> oom_events;
+    if (!memevent_listener.getMemEvents(oom_events)) {
         memevent_listener.deregisterAllEvents();
         jniThrowRuntimeException(env, "Failed to get OOM events");
         return nullptr;
@@ -75,15 +72,23 @@
     }
 
     for (int i = 0; i < oom_events.size(); i++) {
-        const memevents::OomKill oom_event = oom_events[i];
-        jstring process_name = env->NewStringUTF(oom_event.process_name);
+        const mem_event_t mem_event = oom_events[i];
+        if (mem_event.type != MEM_EVENT_OOM_KILL) {
+            memevent_listener.deregisterAllEvents();
+            jniThrowRuntimeException(env, "Received invalid memory event");
+            return java_oom_array;
+        }
+
+        const auto oom_kill = mem_event.event_data.oom_kill;
+
+        jstring process_name = env->NewStringUTF(oom_kill.process_name);
         if (process_name == NULL) {
             memevent_listener.deregisterAllEvents();
             jniThrowRuntimeException(env, "Failed creating java string for process name");
         }
         jobject java_oom_kill = env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor,
-                                               oom_event.timestamp_ms, oom_event.pid, oom_event.uid,
-                                               process_name, oom_event.oom_score_adj);
+                                               oom_kill.timestamp_ms, oom_kill.pid, oom_kill.uid,
+                                               process_name, oom_kill.oom_score_adj);
         if (java_oom_kill == NULL) {
             memevent_listener.deregisterAllEvents();
             jniThrowRuntimeException(env, "Failed to create OomKillRecord object");
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4a6b31c..810090a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -285,6 +285,7 @@
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
     void setPointerDisplayId(int32_t displayId);
+    int32_t getMousePointerSpeed();
     void setPointerSpeed(int32_t speed);
     void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
     void setTouchpadPointerSpeed(int32_t speed);
@@ -1219,6 +1220,11 @@
     }
 }
 
+int32_t NativeInputManager::getMousePointerSpeed() {
+    std::scoped_lock _l(mLock);
+    return mLocked.pointerSpeed;
+}
+
 void NativeInputManager::setPointerSpeed(int32_t speed) {
     { // acquire lock
         std::scoped_lock _l(mLock);
@@ -2211,6 +2217,12 @@
     }
 }
 
+static jint nativeGetMousePointerSpeed(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    return static_cast<jint>(im->getMousePointerSpeed());
+}
+
 static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -2865,6 +2877,7 @@
         {"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
          (void*)nativeTransferTouchFocus},
         {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
+        {"getMousePointerSpeed", "()I", (void*)nativeGetMousePointerSpeed},
         {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
         {"setMousePointerAccelerationEnabled", "(IZ)V",
          (void*)nativeSetMousePointerAccelerationEnabled},
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3dcf42d..b6f7eb3 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -103,14 +103,16 @@
                 flattenedPrimaryProviders.add(cn.flattenToString());
             }
 
+            final boolean isShowAllOptionsRequested = false;
             mPendingIntent = mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newCreateRequestInfo(
                             mRequestId, mClientRequest,
                             mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
-                            /*defaultProviderId=*/flattenedPrimaryProviders),
-                    providerDataList, /*isRequestForAllOptions=*/ false);
+                            /*defaultProviderId=*/flattenedPrimaryProviders,
+                            isShowAllOptionsRequested),
+                    providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
             mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 4203576..84b5cb7 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -148,8 +148,8 @@
      * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
      * by the calling app process.
      *
-     * @param requestInfo      the information about the request
-     * @param providerDataList the list of provider data from remote providers
+     * @param requestInfo            the information about the request
+     * @param providerDataList       the list of provider data from remote providers
      * @param isRequestForAllOptions whether the bottom sheet should directly navigate to the
      *                               all options page
      */
@@ -170,7 +170,8 @@
                 .map(disabledProvider -> new DisabledProviderData(
                         disabledProvider.getComponentName().flattenToString())).toList();
 
-        Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList,
+        Intent intent = IntentFactory.createCredentialSelectorIntent(mContext, requestInfo,
+                        providerDataList,
                         new ArrayList<>(disabledProviderDataList), mResultReceiver,
                         isRequestForAllOptions)
                 .setAction(UUID.randomUUID().toString());
@@ -178,7 +179,7 @@
         // intents
         return PendingIntent.getActivityAsUser(
                 mContext, /*requestCode=*/0, intent,
-                PendingIntent.FLAG_IMMUTABLE, /*options=*/null,
+                PendingIntent.FLAG_MUTABLE, /*options=*/null,
                 UserHandle.of(mUserId));
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 7e709fe..9e362b3 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -111,13 +111,15 @@
         }
 
         cancelExistingPendingIntent();
+        final boolean isShowAllOptionsRequested = true;
         mPendingIntent = mCredentialManagerUi.createPendingIntent(
                 RequestInfo.newGetRequestInfo(
                         mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                         PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
-                providerDataList,
-                /*isRequestForAllOptions=*/ true);
+                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                        isShowAllOptionsRequested),
+                /*providerDataList=*/ null,
+                /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
 
         List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
         for (ProviderData providerData : providerDataList) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index b33f531..4068d7b 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -102,15 +102,17 @@
         Binder.withCleanCallingIdentity(() -> {
             try {
                 cancelExistingPendingIntent();
+                final boolean isShowAllOptionsRequested = false;
                 mPendingIntent = mCredentialManagerUi.createPendingIntent(
                         RequestInfo.newGetRequestInfo(
                                 mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                                 PermissionUtils.hasPermission(mContext,
                                         mClientAppInfo.getPackageName(),
                                         Manifest.permission
-                                                .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                                                .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                                isShowAllOptionsRequested),
                         providerDataList,
-                        /*isRequestForAllOptions=*/ false);
+                        /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
                 mClientCallback.onPendingIntent(mPendingIntent);
             } catch (RemoteException e) {
                 mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 30af567..6b313fd 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -187,12 +187,14 @@
             }
         }
         if (!providerDataList.isEmpty()) {
+            final boolean isShowAllOptionsRequested = false;
             return mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                             mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
-                    providerDataList, /*isRequestForAllOptions=*/ false);
+                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                            isShowAllOptionsRequested),
+                    providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
         } else {
             return null;
         }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index fcaef9f..ca23d62 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -30,6 +30,7 @@
 import android.credentials.selection.Entry;
 import android.credentials.selection.GetCredentialProviderData;
 import android.credentials.selection.ProviderPendingIntentResponse;
+import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.service.autofill.Flags;
 import android.service.credentials.Action;
@@ -76,6 +77,9 @@
     @NonNull
     private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
 
+    @NonNull
+    private final Map<String, AutofillId> mCredentialEntryKeyToAutofilLIdMap;
+
 
     /** The complete request to be used in the second round. */
     private final android.credentials.GetCredentialRequest mCompleteRequest;
@@ -245,6 +249,7 @@
         mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
         mProviderResponseDataHandler = new ProviderResponseDataHandler(
                 ComponentName.unflattenFromString(hybridService));
+        mCredentialEntryKeyToAutofilLIdMap = new HashMap<>();
     }
 
     /** Called when the provider response has been updated by an external source. */
@@ -298,7 +303,7 @@
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
-                onCredentialEntrySelected(providerPendingIntentResponse);
+                onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
                 break;
             case ACTION_ENTRY_KEY:
                 Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
@@ -307,7 +312,7 @@
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
-                onActionEntrySelected(providerPendingIntentResponse);
+                onActionEntrySelected(providerPendingIntentResponse, entryKey);
                 break;
             case AUTHENTICATION_ACTION_ENTRY_KEY:
                 Action authenticationEntry = mProviderResponseDataHandler
@@ -337,7 +342,7 @@
                 break;
             case REMOTE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
-                    onRemoteEntrySelected(providerPendingIntentResponse);
+                    onRemoteEntrySelected(providerPendingIntentResponse, entryKey);
                 } else {
                     Slog.i(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
@@ -376,7 +381,7 @@
         return null;
     }
 
-    private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
+    private Intent setUpFillInIntentWithFinalRequest(@NonNull String id, String entryKey) {
         // TODO: Determine if we should skip this entry if entry id is not set, or is set
         // but does not resolve to a valid option. For now, not skipping it because
         // it may be possible that the provider adds their own extras and expects to receive
@@ -392,6 +397,7 @@
                 .getParcelable(CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
         if (autofillId != null && Flags.autofillCredmanIntegration()) {
             intent.putExtra(CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId);
+            mCredentialEntryKeyToAutofilLIdMap.put(entryKey, autofillId);
         }
         return intent.putExtra(
                 CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -408,12 +414,13 @@
     }
 
     private void onRemoteEntrySelected(
-            ProviderPendingIntentResponse providerPendingIntentResponse) {
-        onCredentialEntrySelected(providerPendingIntentResponse);
+            ProviderPendingIntentResponse providerPendingIntentResponse, String entryKey) {
+        onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
     }
 
     private void onCredentialEntrySelected(
-            ProviderPendingIntentResponse providerPendingIntentResponse) {
+            ProviderPendingIntentResponse providerPendingIntentResponse,
+            String entryKey) {
         if (providerPendingIntentResponse == null) {
             invokeCallbackOnInternalInvalidState();
             return;
@@ -430,7 +437,18 @@
         GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
                 .extractGetCredentialResponse(
                         providerPendingIntentResponse.getResultData());
-        if (getCredentialResponse != null) {
+        if (getCredentialResponse != null && getCredentialResponse.getCredential() != null) {
+            Bundle credentialData = getCredentialResponse.getCredential().getData();
+            AutofillId autofillId = mCredentialEntryKeyToAutofilLIdMap.get(entryKey);
+            if (Flags.autofillCredmanIntegration()
+                    && entryKey != null && autofillId != null && credentialData != null
+            ) {
+                Slog.d(TAG, "Adding autofillId to credential response: " + autofillId);
+                credentialData.putParcelable(
+                        CredentialProviderService.EXTRA_AUTOFILL_ID,
+                        mCredentialEntryKeyToAutofilLIdMap.get(entryKey)
+                );
+            }
             mCallbacks.onFinalResponseReceived(mComponentName,
                     getCredentialResponse);
             return;
@@ -514,9 +532,9 @@
 
     /** Returns true if either an exception or a response is found. */
     private void onActionEntrySelected(ProviderPendingIntentResponse
-            providerPendingIntentResponse) {
+            providerPendingIntentResponse, String entryKey) {
         Slog.i(TAG, "onActionEntrySelected");
-        onCredentialEntrySelected(providerPendingIntentResponse);
+        onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
     }
 
 
@@ -614,7 +632,7 @@
             Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
                     id, credentialEntry.getSlice(),
                     setUpFillInIntentWithFinalRequest(credentialEntry
-                            .getBeginGetCredentialOptionId()));
+                            .getBeginGetCredentialOptionId(), id));
             mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
             mCredentialEntryTypes.add(credentialEntry.getType());
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 519c9bb..74d544f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23128,6 +23128,10 @@
                     mInjector.systemPropertiesSet(memtagProperty, "memtag");
                 } else if (flags == DevicePolicyManager.MTE_DISABLED) {
                     mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+                } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                    if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                        mInjector.systemPropertiesSet(memtagProperty, "default");
+                    }
                 }
                 admin.mtePolicy = flags;
                 saveSettingsLocked(caller.getUserId());
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index d5a3cff..82d5247 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -35,6 +35,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.util.ArraySet;
+import android.util.Dumpable;
 import android.view.Display;
 import android.view.Surface;
 
@@ -43,7 +44,9 @@
 import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
 import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Predicate;
@@ -56,7 +59,7 @@
  * See {@link BookStyleStateTransitions} for detailed description of the default behavior.
  */
 public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
-        DisplayManager.DisplayListener {
+        DisplayManager.DisplayListener, Dumpable {
 
     private final BookStylePreferredScreenCalculator mClosedStateCalculator;
     private final Handler mHandler = new Handler();
@@ -154,6 +157,14 @@
 
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        writer.println("  " + getDumpableName());
+
+        mPostureEstimator.dump(writer, args);
+        mClosedStateCalculator.dump(writer, args);
+    }
+
     public interface ClosedStateUpdatesListener {
         void onClosedStateUpdated();
     }
@@ -161,7 +172,7 @@
     /**
      * Estimates if the device is going to enter wedge/tent mode based on the sensor data
      */
-    private static class PostureEstimator implements SensorEventListener {
+    private static class PostureEstimator implements SensorEventListener, Dumpable {
 
 
         private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8;
@@ -356,6 +367,23 @@
             mDeviceClosed = deviceClosed;
             mConditionedSensorListener.updateListeningState();
         }
+
+        @Override
+        public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+            writer.println("    " + getDumpableName());
+            writer.println("      isLikelyTentOrWedgeMode = " + isLikelyTentOrWedgeMode());
+            writer.println("      mScreenTurnedOn = " + mScreenTurnedOn);
+            writer.println("      mLastScreenRotation = " + mLastScreenRotation);
+            writer.println("      mDeviceClosed = " + mDeviceClosed);
+            writer.println("      mLeftGravityVector = " + Arrays.toString(mLeftGravityVector));
+            writer.println("      mRightGravityVector = " + Arrays.toString(mRightGravityVector));
+        }
+
+        @NonNull
+        @Override
+        public String getDumpableName() {
+            return "PostureEstimator";
+        }
     }
 
     /**
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index ad938af..8b22718 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -38,6 +38,7 @@
 import com.android.server.policy.feature.flags.FeatureFlags;
 import com.android.server.policy.feature.flags.FeatureFlagsImpl;
 
+import java.io.PrintWriter;
 import java.util.function.Predicate;
 
 /**
@@ -182,4 +183,9 @@
     public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
         onComplete.run();
     }
+
+    @Override
+    public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        mProvider.dump(writer, args);
+    }
 }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
index 8977422..69d793e 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
@@ -16,8 +16,14 @@
 
 package com.android.server.policy;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Build;
+import android.util.Dumpable;
+import android.util.Slog;
 
+
+import java.io.PrintWriter;
 import java.util.List;
 import java.util.Objects;
 
@@ -31,7 +37,12 @@
  *
  * See {@link BookStyleStateTransitions} for detailed description of the default behavior.
  */
-public class BookStylePreferredScreenCalculator {
+public class BookStylePreferredScreenCalculator implements Dumpable {
+
+    private static final String TAG = "BookStylePreferredScreenCalculator";
+
+    // TODO(b/322137477): disable by default on all builds after flag clean-up
+    private static final boolean DEBUG = Build.IS_USERDEBUG || Build.IS_ENG;
 
     /**
      * When calculating the new state we will re-calculate it until it settles down. We re-calculate
@@ -77,6 +88,9 @@
      */
     public PreferredScreen calculatePreferredScreen(HingeAngle angle, boolean likelyTentOrWedge,
             boolean likelyReverseWedge) {
+
+        final State oldState = mState;
+
         int attempts = 0;
         State newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge);
         while (attempts < MAX_STATE_CHANGES && !Objects.equals(mState, newState)) {
@@ -92,7 +106,6 @@
                             + ", likelyReverseWedge = " + likelyReverseWedge);
         }
 
-        final State oldState = mState;
         mState = newState;
 
         if (mState.mPreferredScreen == PreferredScreen.INVALID) {
@@ -103,6 +116,13 @@
                             + oldState);
         }
 
+        if (DEBUG && !Objects.equals(oldState, newState)) {
+            Slog.d(TAG, "Moving to state " + mState
+                    + " (hingeAngle = " + angle
+                    + ", likelyTentOrWedge = " + likelyTentOrWedge
+                    + ", likelyReverseWedge = " + likelyReverseWedge + ")");
+        }
+
         return mState.mPreferredScreen;
     }
 
@@ -129,6 +149,18 @@
                         + likelyReverseWedge);
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        writer.println("    " + getDumpableName());
+        writer.println("      mState = " + mState);
+    }
+
+    @NonNull
+    @Override
+    public String getDumpableName() {
+        return TAG;
+    }
+
     /**
      * The angle between two halves of the foldable device in degrees. The angle is '0' when
      * the device is fully closed and '180' when the device is fully open and flat.
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index ba72977..021a667 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -39,6 +39,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Trace;
+import android.util.Dumpable;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -52,6 +53,7 @@
 import com.android.server.policy.feature.flags.FeatureFlags;
 import com.android.server.policy.feature.flags.FeatureFlagsImpl;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -87,6 +89,8 @@
     // the conditions needed for availability.
     private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>();
 
+    private final DeviceStateConfiguration[] mConfigurations;
+
     @GuardedBy("mLock")
     private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray();
 
@@ -142,6 +146,7 @@
         mHingeAngleSensor = hingeAngleSensor;
         mHallSensor = hallSensor;
         mDisplayManager = displayManager;
+        mConfigurations = deviceStateConfigurations;
         mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
 
         sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST);
@@ -350,16 +355,20 @@
     @GuardedBy("mLock")
     private void dumpSensorValues() {
         Slog.i(TAG, "Sensor values:");
-        dumpSensorValues("Hall Sensor", mHallSensor, mLastHallSensorEvent);
-        dumpSensorValues("Hinge Angle Sensor", mHingeAngleSensor, mLastHingeAngleSensorEvent);
+        dumpSensorValues(mHallSensor, mLastHallSensorEvent);
+        dumpSensorValues(mHingeAngleSensor, mLastHingeAngleSensorEvent);
         Slog.i(TAG, "isScreenOn: " + isScreenOn());
     }
 
     @GuardedBy("mLock")
-    private void dumpSensorValues(String sensorType, Sensor sensor, @Nullable SensorEvent event) {
+    private void dumpSensorValues(Sensor sensor, @Nullable SensorEvent event) {
+        Slog.i(TAG, toSensorValueString(sensor, event));
+    }
+
+    private String toSensorValueString(Sensor sensor, @Nullable SensorEvent event) {
         String sensorString = sensor == null ? "null" : sensor.getName();
         String eventValues = event == null ? "null" : Arrays.toString(event.values);
-        Slog.i(TAG, sensorType + " : " + sensorString + " : " + eventValues);
+        return sensorString + " : " + eventValues;
     }
 
     @Override
@@ -414,6 +423,34 @@
         }
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        writer.println("FoldableDeviceStateProvider");
+
+        synchronized (mLock) {
+            writer.println("  mLastReportedState = " + mLastReportedState);
+            writer.println("  mPowerSaveModeEnabled = " + mPowerSaveModeEnabled);
+            writer.println("  mThermalStatus = " + mThermalStatus);
+            writer.println("  mLastHingeAngleSensorEvent = " +
+                    toSensorValueString(mHingeAngleSensor, mLastHingeAngleSensorEvent));
+            writer.println("  mLastHallSensorEvent = " +
+                    toSensorValueString(mHallSensor, mLastHallSensorEvent));
+        }
+
+        writer.println();
+        writer.println("  Predicates:");
+
+        for (int i = 0; i < mConfigurations.length; i++) {
+            final DeviceStateConfiguration configuration = mConfigurations[i];
+            final Predicate<FoldableDeviceStateProvider> predicate =
+                    configuration.mActiveStatePredicate;
+
+            if (predicate instanceof Dumpable dumpable) {
+                dumpable.dump(writer, /* args= */ null);
+            }
+        }
+    }
+
     /**
      * Configuration for a single device state, contains information about the state like
      * identifier, name, flags and a predicate that should return true if the state should
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index fd21a32..2359422c9 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -22,6 +22,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.SignedPackage;
+import android.content.pm.SignedPackageParcel;
 import android.os.Binder;
 import android.os.ISystemConfig;
 import android.util.ArrayMap;
@@ -119,6 +121,26 @@
                             pmi.canQueryPackage(Binder.getCallingUid(), preventUserDisablePackage))
                     .collect(toList());
         }
+
+        @Override
+        public List<SignedPackageParcel> getEnhancedConfirmationTrustedPackages() {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES,
+                    "Caller must hold " + Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES);
+
+            return SystemConfig.getInstance().getEnhancedConfirmationTrustedPackages().stream()
+                    .map(SignedPackage::getData).toList();
+        }
+
+        @Override
+        public List<SignedPackageParcel> getEnhancedConfirmationTrustedInstallers() {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES,
+                    "Caller must hold " + Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES);
+
+            return SystemConfig.getInstance().getEnhancedConfirmationTrustedInstallers().stream()
+                    .map(SignedPackage::getData).toList();
+        }
     };
 
     public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2b8bcc7..c55d709 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,6 +48,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.credentials.CredentialManager;
+import android.credentials.flags.Flags;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
@@ -269,6 +270,8 @@
             "com.android.server.backup.BackupManagerService$Lifecycle";
     private static final String APPWIDGET_SERVICE_CLASS =
             "com.android.server.appwidget.AppWidgetService";
+    private static final String ARC_NETWORK_SERVICE_CLASS =
+            "com.android.server.arc.net.ArcNetworkService";
     private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
             "com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
     private static final String ARC_SYSTEM_HEALTH_SERVICE =
@@ -2069,13 +2072,24 @@
             if (context.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_WIFI)) {
                 // Wifi Service must be started first for wifi-related services.
-                t.traceBegin("StartWifi");
-                mSystemServiceManager.startServiceFromJar(
-                        WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
-                t.traceEnd();
-                t.traceBegin("StartWifiScanning");
-                mSystemServiceManager.startServiceFromJar(
-                        WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+                if (!isArc) {
+                    t.traceBegin("StartWifi");
+                    mSystemServiceManager.startServiceFromJar(
+                            WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+                    t.traceEnd();
+                    t.traceBegin("StartWifiScanning");
+                    mSystemServiceManager.startServiceFromJar(
+                            WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+                    t.traceEnd();
+                }
+            }
+
+            // ARC - ArcNetworkService registers the ARC network stack and replaces the
+            // stock WiFi service in both ARC++ container and ARCVM. Always starts the ARC network
+            // stack regardless of whether FEATURE_WIFI is enabled/disabled (b/254755875).
+            if (isArc) {
+                t.traceBegin("StartArcNetworking");
+                mSystemServiceManager.startService(ARC_NETWORK_SERVICE_CLASS);
                 t.traceEnd();
             }
 
@@ -2782,9 +2796,14 @@
                     DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
                     CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
             if (credentialManagerEnabled) {
-                t.traceBegin("StartCredentialManagerService");
-                mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
-                t.traceEnd();
+                if(isWatch &&
+                  !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+                  Slog.d(TAG, "CredentialManager disabled on wear.");
+                } else {
+                  t.traceBegin("StartCredentialManagerService");
+                  mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+                  t.traceEnd();
+                }
             } else {
                 Slog.d(TAG, "CredentialManager disabled.");
             }
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index fb0fbe8..68038fa 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -49,7 +49,6 @@
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
-import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 
@@ -314,7 +313,7 @@
             Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
             return;
         }
-        aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+        aml.setBatchDexoptStartCallback(Runnable::run,
                 (snapshot, reason, defaultPackages, builder, passedSignal) -> {
                     traceOnDex2oatStart();
                 });
@@ -327,7 +326,7 @@
         // Sample for a fraction of dex2oat runs.
         final int traceFrequency =
             DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
-                "dex2oat_trace_freq", 10);
+                "dex2oat_trace_freq", 25);
         int randomNum = ThreadLocalRandom.current().nextInt(100);
         if (randomNum < traceFrequency) {
             if (DEBUG) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 56423b9..afd6dbd 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_method_framework",
     // 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"
@@ -73,7 +74,6 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "framework",
-        "mockito_ravenwood",
         "ravenwood-runtime",
         "ravenwood-utils",
         "services",
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index e1fd2b3..8a12dcd 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_method_framework",
     // 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"
diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp
index 00850a5..e3919a5 100644
--- a/services/tests/PackageManagerComponentOverrideTests/Android.bp
+++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp
@@ -18,6 +18,7 @@
 // and this is more representative of a real caller. It also uses Mockito extended, and this
 // prevents converting the entire services test module.
 package {
+    default_team: "trendy_team_framework_android_packages",
     // 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"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index ad7af44..f15e533 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // 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"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
index 9921106..6da503d 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // 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"
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index f8accc3..4f27bc2 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -3,6 +3,7 @@
 //########################################################################
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // 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"
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index 129efc6..005cad1 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@
 import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.SparseArray;
 
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -109,6 +111,8 @@
     private IApplicationThread mIApplicationThread;
 
     private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@
         mUserEnabled.put(PRIMARY_USER, true);
         mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
         mUserEnabled.put(SECONDARY_USER, true);
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
 
-        when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+        when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
                 invocation -> {
                     List<Integer> users = new ArrayList<>();
                     final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 811b086..7aa2ff5 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -22,7 +22,9 @@
 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
 import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainSet
 import android.os.Parcel
+import android.os.Process
 import android.platform.test.annotations.Presubmit
 import android.util.AtomicFile
 import android.util.Slog
@@ -173,7 +175,7 @@
             /* stagingManager */ null,
             /* sessionId */ sessionId,
             /* userId */ 456,
-            /* installerUid */ -1,
+            /* installerUid */ Process.myUid(),
             /* installSource */ installSource,
             /* sessionParams */ params,
             /* createdMillis */ 0L,
@@ -183,8 +185,8 @@
             /* files */ null,
             /* checksums */ null,
             /* prepared */ true,
-            /* committed */ true,
-            /* destroyed */ staged,
+            /* committed */ false,
+            /* destroyed */ false,
             /* sealed */ false, // Setting to true would trigger some PM logic.
             /* childSessionIds */ childSessionIds.toIntArray(),
             /* parentSessionId */ parentSessionId,
@@ -192,7 +194,8 @@
             /* isFailed */ false,
             /* isApplied */ false,
             /* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
-            /* stagedSessionErrorMessage */ "some error"
+            /* stagedSessionErrorMessage */ "some error",
+            /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
         )
     }
 
@@ -332,6 +335,7 @@
         assertThat(expected.parentSessionId).isEqualTo(actual.parentSessionId)
         assertThat(expected.childSessionIds).asList()
             .containsExactlyElementsIn(actual.childSessionIds.toList())
+        assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains)
     }
 
     private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 6fff012..8ad557c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -58,10 +58,10 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 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.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -1043,6 +1043,27 @@
                 UserHandle.SYSTEM).getArchiveState()).isEqualTo(archiveState);
     }
 
+    @RequiresFlagsEnabled(Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
+    @Test
+    public void testWriteReadAppMetadataSource() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        packageSetting.setAppMetadataSource(PackageManager.APP_METADATA_SOURCE_INSTALLER);
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /*sync=*/true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getAppMetadataSource(),
+                is(PackageManager.APP_METADATA_SOURCE_INSTALLER));
+    }
+
     @Test
     public void testPackageRestrictionsDistractionFlagsDefault() {
         final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 85059838..c93f482 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // 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"
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 ef9c62f..cfe701f 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
@@ -272,7 +272,8 @@
         AndroidPackage::hasPreserveLegacyExternalStorage,
         AndroidPackage::hasRequestForegroundServiceExemption,
         AndroidPackage::hasRequestRawExternalStorageAccess,
-        AndroidPackage::isUpdatableSystem
+        AndroidPackage::isUpdatableSystem,
+        AndroidPackage::getEmergencyInstaller
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 93bdeae..d538f25 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -40,6 +40,7 @@
         ParsedProcess::getGwpAsanMode,
         ParsedProcess::getMemtagMode,
         ParsedProcess::getNativeHeapZeroInitialized,
+        ParsedProcess::isUseEmbeddedDex,
     )
 
     override fun extraParams() = listOf(
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 75febd9..b142334 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -72,6 +72,7 @@
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -102,7 +103,10 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.UserManager;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.util.SparseArray;
 import android.view.ContentRecordingSession;
 import android.view.Display;
 import android.view.DisplayAdjustments;
@@ -209,6 +213,10 @@
 
     private int mPreferredHdrOutputType;
 
+    private Handler mPowerHandler;
+
+    private UserManager mUserManager;
+
     private final DisplayManagerService.Injector mShortMockedInjector =
             new DisplayManagerService.Injector() {
                 @Override
@@ -369,11 +377,14 @@
         mContext = spy(new ContextWrapper(
                 ApplicationProvider.getApplicationContext().createDisplayContext(display)));
         mResources = Mockito.spy(mContext.getResources());
+        mPowerHandler = new Handler(Looper.getMainLooper());
         manageDisplaysPermission(/* granted= */ false);
         when(mContext.getResources()).thenReturn(mResources);
+        mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
 
         VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
         when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         // Disable binder caches in this process.
         PropertyInvalidatedCache.disableForTestMode();
         setUpDisplay();
@@ -432,7 +443,7 @@
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -507,7 +518,7 @@
             assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
         }
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -559,7 +570,7 @@
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -594,7 +605,7 @@
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -632,7 +643,7 @@
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -667,7 +678,7 @@
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -947,7 +958,7 @@
                 PACKAGE_NAME);
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1439,7 +1450,7 @@
                 PACKAGE_NAME);
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1694,7 +1705,7 @@
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
 
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
 
         // flush the handler
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1728,7 +1739,7 @@
                 null /* projection */, PACKAGE_NAME);
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
         DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
         assertNotNull(ddi);
@@ -1797,7 +1808,7 @@
                 mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
         verify(mMockProjectionService, never()).setContentRecordingSession(any(),
                 nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        performTraversalInternal(displayManager);
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
         DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
         assertNotNull(ddi);
@@ -2788,6 +2799,85 @@
         assertThat(display.getDisplayOffloadSessionLocked()).isNull();
     }
 
+    @Test
+    public void testOnUserSwitching_UpdatesBrightness() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        float brightness1 = 0.3f;
+        float brightness2 = 0.45f;
+
+        int userId1 = 123;
+        int userId2 = 456;
+        UserInfo userInfo1 = new UserInfo();
+        userInfo1.id = userId1;
+        UserInfo userInfo2 = new UserInfo();
+        userInfo2.id = userId2;
+        when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+        when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+        final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+        final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+        // The same brightness will be restored for a user only if auto-brightness is off,
+        // otherwise the current lux will be used to determine the brightness.
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        displayManager.onUserSwitching(to, from);
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness1);
+        displayManager.onUserSwitching(from, to);
+        waitForIdleHandler(mPowerHandler);
+        displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness2);
+
+        displayManager.onUserSwitching(to, from);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(brightness1,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+
+        displayManager.onUserSwitching(from, to);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(brightness2,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testOnUserSwitching_brightnessForNewUserIsDefault() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        int userId1 = 123;
+        int userId2 = 456;
+        UserInfo userInfo1 = new UserInfo();
+        userInfo1.id = userId1;
+        UserInfo userInfo2 = new UserInfo();
+        userInfo2.id = userId2;
+        when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+        when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+        final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+        final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+        displayManager.onUserSwitching(from, to);
+        waitForIdleHandler(mPowerHandler);
+        assertEquals(displayManagerBinderService.getDisplayInfo(Display.DEFAULT_DISPLAY)
+                        .brightnessDefault,
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+                FLOAT_TOLERANCE);
+    }
+
     private void initDisplayPowerController(DisplayManagerInternal localService) {
         localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
             @Override
@@ -2819,7 +2909,7 @@
             public void releaseSuspendBlocker(String id) {
 
             }
-        }, new Handler(Looper.getMainLooper()), mSensorManager);
+        }, mPowerHandler, mSensorManager);
     }
 
     private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
@@ -2912,6 +3002,11 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
+    private void performTraversalInternal(DisplayManagerService displayManager) {
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class),
+                new SparseArray<>());
+    }
+
     private int getDisplayIdForDisplayDevice(
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 88a9758..57b8632 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -227,18 +227,14 @@
         advanceTime(1);
 
         // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+        verify(mHolder.wakelockController).acquireWakelock(
                 WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
         verify(mHolder.wakelockController).acquireWakelock(
                 WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
 
         mHolder.dpc.stop();
         advanceTime(1);
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+        verify(mHolder.wakelockController).releaseAll();
     }
 
     @Test
@@ -515,9 +511,8 @@
 
         verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
                 eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
-        verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
-        verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+        verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
                 eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
 
         when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
@@ -811,7 +806,7 @@
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+        verify(mHolder.displayPowerState).setScreenState(anyInt());
 
         mHolder = createDisplayPowerController(42, UNIQUE_ID);
 
@@ -1024,15 +1019,14 @@
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        // One triggered by the test, the other by handleBrightnessModeChange
-        verify(mHolder.automaticBrightnessController, times(2)).configure(
+        verify(mHolder.automaticBrightnessController).configure(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
                 /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
                 /* shouldResetShortTermModel= */ false
         );
-        verify(mHolder.hbmController, times(2))
+        verify(mHolder.hbmController)
                 .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
     }
 
@@ -1098,8 +1092,7 @@
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        // One triggered by the test, the other by handleBrightnessModeChange
-        verify(mHolder.automaticBrightnessController, times(2)).configure(
+        verify(mHolder.automaticBrightnessController).configure(
                 AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
                 /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 /* userChangedBrightness= */ false, /* adjustment= */ 0,
@@ -1136,8 +1129,7 @@
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
-        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
-        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
+        verify(mHolder.animator).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
                 eq(false));
     }
 
@@ -1210,8 +1202,9 @@
         when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
 
         mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
+        mHolder.dpc.setBrightness(0.8f, /* userSerial= */ 123);
 
-        verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
+        verify(mHolder.brightnessSetting, times(2)).setBrightness(clampedBrightness);
     }
 
     @Test
@@ -1564,9 +1557,7 @@
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        // One triggered by handleBrightnessModeChange, another triggered by
-        // setBrightnessFromOffload
-        verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
                 eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
     }
 
@@ -1580,9 +1571,7 @@
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
-        verify(mHolder.automaticBrightnessController, times(2))
-                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+        verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
 
         // Back to default mode
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -1620,6 +1609,62 @@
                 .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
     }
 
+    @Test
+    public void testOnSwitchUserUpdatesBrightness() {
+        int userSerial = 12345;
+        float brightness = 0.65f;
+
+        mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+        advanceTime(1);
+
+        verify(mHolder.brightnessSetting).setUserSerial(userSerial);
+        verify(mHolder.brightnessSetting).setBrightness(brightness);
+    }
+
+    @Test
+    public void testOnSwitchUserDoesNotAddUserDataPoint() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        int userSerial = 12345;
+        float brightness = 0.65f;
+        when(mHolder.automaticBrightnessController.hasValidAmbientLux()).thenReturn(true);
+        when(mHolder.automaticBrightnessController.convertToAdjustedNits(brightness))
+                .thenReturn(500f);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+
+        verify(mHolder.automaticBrightnessController, never()).configure(
+                /* state= */ anyInt(),
+                /* configuration= */ any(),
+                eq(brightness),
+                /* userChangedBrightness= */ eq(true),
+                /* adjustment= */ anyFloat(),
+                /* userChangedAutoBrightnessAdjustment= */ anyBoolean(),
+                /* displayPolicy= */ anyInt(),
+                /* shouldResetShortTermModel= */ anyBoolean());
+        verify(mBrightnessTrackerMock, never()).notifyBrightnessChanged(
+                /* brightness= */ anyFloat(),
+                /* userInitiated= */ eq(true),
+                /* powerBrightnessFactor= */ anyFloat(),
+                /* wasShortTermModelActive= */ anyBoolean(),
+                /* isDefaultBrightnessConfig= */ anyBoolean(),
+                /* uniqueDisplayId= */ any(),
+                /* luxValues */ any(),
+                /* luxTimestamps= */ any());
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index c92ce25..b99ecf3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -178,8 +178,8 @@
                 .thenReturn(mockArray);
         when(mMockedResources.obtainTypedArray(R.array.config_displayCutoutSideOverrideArray))
                 .thenReturn(mockArray);
-        when(mMockedResources.getStringArray(R.array.config_mainBuiltInDisplayCutoutSideOverride))
-                .thenReturn(new String[]{});
+        when(mMockedResources.getIntArray(R.array.config_mainBuiltInDisplayCutoutSideOverride))
+                .thenReturn(new int[]{});
         when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
                 .thenReturn(mockArray);
         when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 2d0c3fd..289d54b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -252,7 +251,6 @@
                 0.0f);
         verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
         verify(mBrightnessSetting).setBrightness(brightnessValue);
-        verify(mBrightnessSetting).setUserSerial(anyInt());
 
         // Does nothing if the value is invalid
         mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 78ec2ff3..5408e11 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -104,7 +104,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -127,7 +127,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -150,7 +150,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -173,7 +173,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -196,7 +196,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -221,7 +221,7 @@
         boolean userSetBrightnessChanged = true;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -247,7 +247,7 @@
         float pendingBrightnessAdjustment = 0.1f;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -411,7 +411,7 @@
     private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
     }
 
     private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index d928306..9f97551 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -73,6 +73,7 @@
         "testng",
         "compatibility-device-util-axt",
         "flag-junit",
+        "am_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index f875f65..fb47aa8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -38,6 +38,8 @@
 import android.os.HandlerThread;
 import android.os.TestLooperManager;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.util.SparseArray;
 
@@ -93,6 +95,9 @@
             .spyStatic(ProcessList.class)
             .build();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
 
     @Mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e7aaed4..3f6117b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -75,6 +75,8 @@
 import android.os.PowerExemptionManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
@@ -135,6 +137,17 @@
             ProcessStartBehavior.SUCCESS);
 
     /**
+     * Map of processes to behaviors indicating how the new processes should behave as needed
+     * by the tests.
+     */
+    private ArrayMap<String, ProcessBehavior> mNewProcessBehaviors = new ArrayMap<>();
+
+    /**
+     * Map of processes to behaviors indicating how the new process starts should result in.
+     */
+    private ArrayMap<String, ProcessStartBehavior> mNewProcessStartBehaviors = new ArrayMap<>();
+
+    /**
      * Collection of all active processes during current test run.
      */
     private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
@@ -161,15 +174,17 @@
             Log.v(TAG, "Intercepting startProcessLocked() for "
                     + Arrays.toString(invocation.getArguments()));
             assertHealth();
-            final ProcessStartBehavior behavior = mNextProcessStartBehavior
-                    .getAndSet(ProcessStartBehavior.SUCCESS);
+            final String processName = invocation.getArgument(0);
+            final ProcessStartBehavior behavior = mNewProcessStartBehaviors.getOrDefault(
+                    processName, mNextProcessStartBehavior.getAndSet(ProcessStartBehavior.SUCCESS));
             if (behavior == ProcessStartBehavior.FAIL_NULL) {
                 return null;
             }
-            final String processName = invocation.getArgument(0);
             final ApplicationInfo ai = invocation.getArgument(1);
+            final ProcessBehavior processBehavior = mNewProcessBehaviors.getOrDefault(
+                    processName, ProcessBehavior.NORMAL);
             final ProcessRecord res = makeActiveProcessRecord(ai, processName,
-                    ProcessBehavior.NORMAL, UnaryOperator.identity());
+                    processBehavior, UnaryOperator.identity());
             final ProcessRecord deliverRes;
             switch (behavior) {
                 case SUCCESS_PREDECESSOR:
@@ -236,10 +251,6 @@
         }).when(mAms).registerUidObserver(any(), anyInt(),
                 eq(ActivityManager.PROCESS_STATE_TOP), any());
 
-        mConstants.TIMEOUT = 200;
-        mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
-        mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
-
         final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
             public void addBroadcastToHistoryLocked(BroadcastRecord original) {
                 // Ignored
@@ -259,6 +270,12 @@
         mBroadcastQueues[0] = mQueue;
 
         mQueue.start(mContext.getContentResolver());
+
+        // Set the constants after invoking BroadcastQueue.start() to ensure they don't
+        // get overridden by the defaults.
+        mConstants.TIMEOUT = 200;
+        mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
+        mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
     }
 
     @After
@@ -272,6 +289,8 @@
             assertEquals(app.toShortString(), ProcessList.SCHED_GROUP_UNDEFINED,
                     mQueue.getPreferredSchedulingGroupLocked(app));
         }
+        mNewProcessBehaviors.clear();
+        mNewProcessStartBehaviors.clear();
     }
 
     @Override
@@ -953,6 +972,40 @@
     }
 
     /**
+     * Verify that we handle manifest receivers in a process that always
+     * responds with {@link DeadObjectException} even after restarting.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+    public void testRepeatedDead_Manifest() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        mNewProcessBehaviors.put(PACKAGE_GREEN, ProcessBehavior.DEAD);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                withPriority(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0))));
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+        waitForIdle();
+
+        final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        // Modern queue always kills the target process when broadcast delivery fails, where as
+        // the legacy queue leaves the process killing task to AMS
+        if (mImpl == Impl.MODERN) {
+            assertNull(receiverGreenApp);
+        }
+        final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+                getUidForPackage(PACKAGE_BLUE));
+        verifyScheduleReceiver(receiverBlueApp, airplane);
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        verifyScheduleReceiver(receiverYellowApp, timezone);
+    }
+
+    /**
      * Verify that we handle the system failing to start a process.
      */
     @Test
@@ -1140,6 +1193,49 @@
     }
 
     /**
+     * Verify that when BroadcastQueue doesn't get notified when a process gets killed repeatedly,
+     * it doesn't get stuck.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+    public void testRepeatedKillWithoutNotify() throws Exception {
+        // Legacy queue does not handle repeated kills that don't get notified.
+        Assume.assumeTrue(mImpl == Impl.MODERN);
+
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+        mNewProcessStartBehaviors.put(PACKAGE_GREEN, ProcessStartBehavior.KILLED_WITHOUT_NOTIFY);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+                withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+        waitForIdle();
+        final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+                getUidForPackage(PACKAGE_ORANGE));
+
+        // Modern queue always kills the target process when broadcast delivery fails, where as
+        // the legacy queue leaves the process killing task to AMS
+        if (mImpl == Impl.MODERN) {
+            assertNull(receiverGreenApp);
+        }
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+        verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+        verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+    }
+
+    /**
      * Verify that a broadcast sent to a frozen app, which gets killed as part of unfreezing
      * process due to pending sync binder transactions, is delivered as expected.
      */
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 eaf0ffd..3355a6c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -29,6 +29,8 @@
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -60,12 +62,15 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
 import android.os.BatteryManagerInternal;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
@@ -82,6 +87,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
@@ -105,6 +111,9 @@
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private class TestJobSchedulerService extends JobSchedulerService {
         TestJobSchedulerService(Context context) {
             super(context);
@@ -1711,6 +1720,262 @@
 
     /** Tests that rare job batching works as expected. */
     @Test
+    public void testConnectivityJobBatching() {
+        mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+        spyOn(mService);
+        doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+        doNothing().when(mService).noteJobsPending(any());
+        doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+        ConnectivityController connectivityController = mService.getConnectivityController();
+        spyOn(connectivityController);
+        advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+        JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+                mService.new MaybeReadyJobQueueFunctor();
+        mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+        mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+                .put(NetworkCapabilities.TRANSPORT_CELLULAR, 5);
+        mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+                .put(NetworkCapabilities.TRANSPORT_WIFI, 2);
+        mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+        final Network network = mock(Network.class);
+
+        // Not enough connectivity jobs to run.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        NetworkCapabilities capabilities = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .build();
+        doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+        doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+        for (int i = 0; i < 4; ++i) {
+            JobStatus job = createJobStatus(
+                    "testConnectivityJobBatching",
+                    createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+            job.setStandbyBucket(ACTIVE_INDEX);
+            job.network = network;
+
+            maybeQueueFunctor.accept(job);
+            assertNull(maybeQueueFunctor.mBatches.get(null));
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(0, mService.getPendingJobQueue().size());
+
+        // Not enough connectivity jobs to run, but the network is already active
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+        doReturn(true).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+        for (int i = 0; i < 4; ++i) {
+            JobStatus job = createJobStatus(
+                    "testConnectivityJobBatching",
+                    createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+            job.setStandbyBucket(ACTIVE_INDEX);
+            job.network = network;
+
+            maybeQueueFunctor.accept(job);
+            assertNull(maybeQueueFunctor.mBatches.get(null));
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(0, job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(4, mService.getPendingJobQueue().size());
+
+        // Enough connectivity jobs to run.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        capabilities = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
+        doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+        doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+        for (int i = 0; i < 3; ++i) {
+            JobStatus job = createJobStatus(
+                    "testConnectivityJobBatching",
+                    createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+            job.setStandbyBucket(ACTIVE_INDEX);
+            job.network = network;
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(3, mService.getPendingJobQueue().size());
+
+        // Not enough connectivity jobs to run, but a non-batched job saves the day.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        JobStatus runningJob = createJobStatus(
+                "testConnectivityJobBatching",
+                createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        runningJob.network = network;
+        doReturn(true).when(mService).isCurrentlyRunningLocked(runningJob);
+        capabilities = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .build();
+        doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+        for (int i = 0; i < 3; ++i) {
+            JobStatus job = createJobStatus(
+                    "testConnectivityJobBatching",
+                    createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+            job.setStandbyBucket(ACTIVE_INDEX);
+            job.network = network;
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.accept(runningJob);
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(3, mService.getPendingJobQueue().size());
+
+        // Not enough connectivity jobs to run, but an old connectivity job saves the day.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        JobStatus oldConnJob = createJobStatus("testConnectivityJobBatching",
+                createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        oldConnJob.network = network;
+        final long oldBatchTime = sElapsedRealtimeClock.millis()
+                - 2 * mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+        oldConnJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+        for (int i = 0; i < 2; ++i) {
+            JobStatus job = createJobStatus(
+                    "testConnectivityJobBatching",
+                    createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+            job.setStandbyBucket(ACTIVE_INDEX);
+            job.network = network;
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.accept(oldConnJob);
+        assertEquals(oldBatchTime, oldConnJob.getFirstForceBatchedTimeElapsed());
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(3, mService.getPendingJobQueue().size());
+
+        // Transport type doesn't have a set threshold. One job should be the default threshold.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        capabilities = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+                .build();
+        doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+        JobStatus job = createJobStatus(
+                "testConnectivityJobBatching",
+                createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        job.setStandbyBucket(ACTIVE_INDEX);
+        job.network = network;
+        maybeQueueFunctor.accept(job);
+        assertEquals(1, maybeQueueFunctor.mBatches.get(network).size());
+        assertEquals(1, maybeQueueFunctor.runnableJobs.size());
+        assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(1, mService.getPendingJobQueue().size());
+    }
+
+    /** Tests that active job batching works as expected. */
+    @Test
+    public void testActiveJobBatching_activeBatchingEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
+
+        spyOn(mService);
+        doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+        doNothing().when(mService).noteJobsPending(any());
+        doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+        advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+        JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+                mService.new MaybeReadyJobQueueFunctor();
+        mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT = 5;
+        mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+        // Not enough ACTIVE jobs to run.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+            JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+            job.setStandbyBucket(ACTIVE_INDEX);
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(0, mService.getPendingJobQueue().size());
+
+        // Enough ACTIVE jobs to run.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT; ++i) {
+            JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+            job.setStandbyBucket(ACTIVE_INDEX);
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(5, mService.getPendingJobQueue().size());
+
+        // Not enough ACTIVE jobs to run, but a non-batched job saves the day.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        JobStatus expeditedJob = createJobStatus("testActiveJobBatching",
+                createJobInfo().setExpedited(true));
+        spyOn(expeditedJob);
+        when(expeditedJob.shouldTreatAsExpeditedJob()).thenReturn(true);
+        expeditedJob.setStandbyBucket(RARE_INDEX);
+        for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+            JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+            job.setStandbyBucket(ACTIVE_INDEX);
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.accept(expeditedJob);
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(3, mService.getPendingJobQueue().size());
+
+        // Not enough ACTIVE jobs to run, but an old ACTIVE job saves the day.
+        mService.getPendingJobQueue().clear();
+        maybeQueueFunctor.reset();
+        JobStatus oldActiveJob = createJobStatus("testActiveJobBatching", createJobInfo());
+        oldActiveJob.setStandbyBucket(ACTIVE_INDEX);
+        final long oldBatchTime = sElapsedRealtimeClock.millis()
+                - 2 * mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+        oldActiveJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+        for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+            JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+            job.setStandbyBucket(ACTIVE_INDEX);
+
+            maybeQueueFunctor.accept(job);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+            assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+            assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+        }
+        maybeQueueFunctor.accept(oldActiveJob);
+        assertEquals(oldBatchTime, oldActiveJob.getFirstForceBatchedTimeElapsed());
+        maybeQueueFunctor.postProcessLocked();
+        assertEquals(3, mService.getPendingJobQueue().size());
+    }
+
+    /** Tests that rare job batching works as expected. */
+    @Test
     public void testRareJobBatching() {
         spyOn(mService);
         doReturn(false).when(mService).evaluateControllerStatesLocked(any());
@@ -1723,17 +1988,15 @@
         mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
         mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
 
-        JobStatus job = createJobStatus(
-                "testRareJobBatching",
-                createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
-        job.setStandbyBucket(RARE_INDEX);
-
         // Not enough RARE jobs to run.
         mService.getPendingJobQueue().clear();
         maybeQueueFunctor.reset();
         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+            JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+            job.setStandbyBucket(RARE_INDEX);
+
             maybeQueueFunctor.accept(job);
-            assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
         }
@@ -1744,8 +2007,11 @@
         mService.getPendingJobQueue().clear();
         maybeQueueFunctor.reset();
         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
+            JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+            job.setStandbyBucket(RARE_INDEX);
+
             maybeQueueFunctor.accept(job);
-            assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
         }
@@ -1753,15 +2019,17 @@
         assertEquals(5, mService.getPendingJobQueue().size());
 
         // Not enough RARE jobs to run, but a non-batched job saves the day.
+        mSetFlagsRule.disableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
         mService.getPendingJobQueue().clear();
         maybeQueueFunctor.reset();
-        JobStatus activeJob = createJobStatus(
-                "testRareJobBatching",
-                createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus activeJob = createJobStatus("testRareJobBatching", createJobInfo());
         activeJob.setStandbyBucket(ACTIVE_INDEX);
         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+            JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+            job.setStandbyBucket(RARE_INDEX);
+
             maybeQueueFunctor.accept(job);
-            assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
         }
@@ -1778,8 +2046,11 @@
                 - 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
         oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+            JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+            job.setStandbyBucket(RARE_INDEX);
+
             maybeQueueFunctor.accept(job);
-            assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+            assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 4958f1c..f27d0c2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -36,6 +36,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
 import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -69,6 +70,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager.OnNetworkActiveListener;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
@@ -102,6 +104,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.time.Clock;
+import java.time.Duration;
 import java.time.ZoneOffset;
 import java.util.Set;
 
@@ -1650,6 +1653,141 @@
         assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
     }
 
+    @Test
+    public void testIsNetworkInStateForJobRunLocked_JobStatus() {
+        mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+        final ConnectivityController controller = new ConnectivityController(mService,
+                mFlexibilityController);
+
+        // Null network
+        final JobStatus expeditedJob =
+                spy(createJobStatus(createJob().setExpedited(true), UID_RED));
+        doReturn(true).when(expeditedJob).shouldTreatAsExpeditedJob();
+        final JobStatus highProcJob = spy(createJobStatus(createJob(), UID_BLUE));
+        doReturn(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE)
+                .when(mService).getUidProcState(eq(UID_BLUE));
+        doReturn(ActivityManager.PROCESS_STATE_CACHED_EMPTY)
+                .when(mService).getUidProcState(eq(UID_RED));
+        final JobStatus regJob = createJobStatus(createJob(), UID_RED);
+        final JobStatus uiJob = spy(createJobStatus(
+                createJob().setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY),
+                UID_RED));
+        doReturn(true).when(uiJob).shouldTreatAsUserInitiatedJob();
+        assertFalse(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(highProcJob));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+        // Privileged jobs are exempted
+        expeditedJob.network = mock(Network.class);
+        highProcJob.network = mock(Network.class);
+        regJob.network = mock(Network.class);
+        uiJob.network = mock(Network.class);
+        assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+        assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+        assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+        mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+        assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+        assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+        assertTrue(controller.isNetworkInStateForJobRunLocked(regJob));
+        assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+    }
+
+    @Test
+    public void testIsNetworkInStateForJobRunLocked_Network() {
+        mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+        final ArgumentCaptor<NetworkCallback> allNetworkCallbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        doNothing().when(mConnManager)
+                .registerNetworkCallback(any(), allNetworkCallbackCaptor.capture());
+        final ArgumentCaptor<OnNetworkActiveListener> onNetworkActiveListenerCaptor =
+                ArgumentCaptor.forClass(OnNetworkActiveListener.class);
+        doNothing().when(mConnManager).addDefaultNetworkActiveListener(
+                onNetworkActiveListenerCaptor.capture());
+        final ArgumentCaptor<NetworkCallback> systemDefaultNetworkCallbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        doNothing().when(mConnManager).registerSystemDefaultNetworkCallback(
+                systemDefaultNetworkCallbackCaptor.capture(), any());
+
+        final ConnectivityController controller = new ConnectivityController(mService,
+                mFlexibilityController);
+
+        assertTrue(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+        mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+        // Unknown network
+        assertFalse(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+        final Network systemDefaultNetwork = mock(Network.class);
+        final Network otherNetwork = mock(Network.class);
+
+        controller.startTrackingLocked();
+
+        final NetworkCallback allNetworkCallback = allNetworkCallbackCaptor.getValue();
+        final OnNetworkActiveListener onNetworkActiveListener =
+                onNetworkActiveListenerCaptor.getValue();
+        final NetworkCallback systemDefaultNetworkCallback =
+                systemDefaultNetworkCallbackCaptor.getValue();
+
+        // No capabilities set
+        allNetworkCallback.onAvailable(systemDefaultNetwork);
+        allNetworkCallback.onAvailable(otherNetwork);
+        assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        // Capabilities set, but never active.
+        allNetworkCallback.onCapabilitiesChanged(
+                systemDefaultNetwork, mock(NetworkCapabilities.class));
+        allNetworkCallback.onCapabilitiesChanged(otherNetwork, mock(NetworkCapabilities.class));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        // Mark system default network as active before identifying system default network.
+        onNetworkActiveListener.onNetworkActive();
+        assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        // Identify system default network and mark as active.
+        systemDefaultNetworkCallback.onAvailable(systemDefaultNetwork);
+        onNetworkActiveListener.onNetworkActive();
+        assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS - 1);
+        assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        // Network stays active beyond expected timeout.
+        advanceElapsedClock(2);
+        doReturn(true).when(mConnManager).isDefaultNetworkActive();
+        assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        // Network becomes inactive after expected timeout.
+        advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+        doReturn(false).when(mConnManager).isDefaultNetworkActive();
+        assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+        // Other network hasn't received a signal for a long time. System default network has
+        // been active within the max wait time.
+        advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+                - controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+        doReturn(false).when(mConnManager).isDefaultNetworkActive();
+        assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+        assertTrue(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+                JobSchedulerService.sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+    }
+
     private void answerNetwork(@NonNull NetworkCallback generalCallback,
             @Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
             @Nullable Network net, @Nullable NetworkCapabilities caps) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
index 9a143d5..1e81951 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
@@ -189,7 +189,7 @@
         assertThat(response.geoidHeightMeters).isWithin(2).of(-5.0622);
         assertThat(response.geoidHeightErrorMeters).isGreaterThan(0f);
         assertThat(response.geoidHeightErrorMeters).isLessThan(1f);
-        assertThat(response.expirationDistanceMeters).isWithin(1).of(-6.33);
+        assertThat(response.expirationDistanceMeters).isWithin(1).of(120490);
         assertThat(response.additionalGeoidHeightErrorMeters).isGreaterThan(0f);
         assertThat(response.additionalGeoidHeightErrorMeters).isLessThan(1f);
         assertThat(response.success).isTrue();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index bf00b75..4535ece 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -270,7 +270,7 @@
         assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
         assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
                 PackageInstaller.STATUS_FAILURE);
-        assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).isEqualTo(
+        assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).contains(
                 String.format("Package %s not found.", PACKAGE));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index d2547a3..6f9b8df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -755,7 +755,8 @@
                 /* isFailed */ false,
                 /* isApplied */false,
                 /* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
-                /* stagedSessionErrorMessage */ "no error");
+                /* stagedSessionErrorMessage */ "no error",
+                /* preVerifiedDomains */ null);
 
         StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
         doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f49f638..64fef68 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -81,7 +81,6 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "truth",
-        "mockito_ravenwood",
     ],
     srcs: [
         ":power_stats_ravenwood_tests",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 5611415..ad6e2c6 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -153,7 +153,6 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.rules",
-        "mockito_ravenwood",
         "services.core",
     ],
     srcs: [
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 6537d47..f9c6794 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -47,6 +47,7 @@
             deleteAppWithParent='false'
             alwaysVisible='true'
             crossProfileContentSharingStrategy='0'
+            itemsRestrictedOnHomeScreen='true'
         />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 2dfabd0..b12d6da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -893,13 +893,13 @@
 
     @Test
     public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
-        final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID);
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
         assertEquals(token, mMockHostToken);
     }
 
     @Test
     public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
-        final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID);
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
         assertNull(token);
     }
 
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 52726ca..b224773 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
@@ -42,6 +42,7 @@
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
 import android.accessibilityservice.MagnificationConfig;
+import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -52,11 +53,15 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Looper;
 import android.os.UserHandle;
+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.view.DisplayInfo;
 import android.view.MagnificationSpec;
 import android.view.accessibility.MagnificationAnimationCallback;
+import android.widget.Scroller;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +70,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -74,6 +80,7 @@
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -104,6 +111,9 @@
     static final int INVALID_DISPLAY = 2;
     private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
             mock(FullScreenMagnificationController.ControllerContext.class);
     final Context mMockContext = mock(Context.class);
@@ -118,6 +128,7 @@
     private MagnificationScaleProvider mScaleProvider;
     private MockContentResolver mResolver;
     private final MagnificationThumbnail mMockThumbnail = mock(MagnificationThumbnail.class);
+    private final Scroller mMockScroller = mock(Scroller.class);
 
     private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass(
             MagnificationConfig.class);
@@ -126,6 +137,8 @@
     ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
     ValueAnimator.AnimatorListener mStateListener;
 
+    private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+
     FullScreenMagnificationController mFullScreenMagnificationController;
 
     public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -134,7 +147,8 @@
 
     @Before
     public void setUp() {
-        Looper looper = InstrumentationRegistry.getContext().getMainLooper();
+        Context realContext = InstrumentationRegistry.getContext();
+        Looper looper = realContext.getMainLooper();
         // Pretending ID of the Thread associated with looper as main thread ID in controller
         when(mMockContext.getMainLooper()).thenReturn(looper);
         when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -168,7 +182,9 @@
                         mRequestObserver,
                         mScaleProvider,
                         () -> mMockThumbnail,
-                        ConcurrentUtils.DIRECT_EXECUTOR);
+                        ConcurrentUtils.DIRECT_EXECUTOR,
+                        () -> mMockScroller,
+                        () -> mMockTimeAnimator);
     }
 
     @After
@@ -428,7 +444,7 @@
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
         mStateListener.onAnimationEnd(mMockValueAnimator);
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
-        verify(mAnimationCallback).onResult(true);
+        verify(mAnimationCallback).onResult(eq(true), any());
     }
 
     @Test
@@ -451,7 +467,7 @@
         mMessageCapturingHandler.sendAllMessages();
 
         verify(mMockValueAnimator, never()).start();
-        verify(mAnimationCallback).onResult(true);
+        verify(mAnimationCallback).onResult(eq(true), any());
     }
 
     @Test
@@ -653,6 +669,85 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+    public void testStartFling_whileMagnifying_flings() throws InterruptedException {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            startFling_whileMagnifying_flings(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void startFling_whileMagnifying_flings(int displayId) throws InterruptedException {
+        register(displayId);
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        float scale = 2.0f;
+        // First zoom in
+        assertTrue(mFullScreenMagnificationController
+                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_1));
+        mMessageCapturingHandler.sendAllMessages();
+
+        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        mFullScreenMagnificationController.startFling(displayId,
+                /* xPixelsPerSecond= */ 400f,
+                /* yPixelsPerSecond= */ 100f,
+                SERVICE_ID_1
+        );
+        mMessageCapturingHandler.sendAllMessages();
+
+        verify(mMockTimeAnimator).start();
+        verify(mMockScroller).fling(
+                /* startX= */ eq((int) newOffsets.x / 2),
+                /* startY= */ eq((int) newOffsets.y / 2),
+                /* velocityX= */ eq(400),
+                /* velocityY= */ eq(100),
+                /* minX= */ anyInt(),
+                /* minY= */ anyInt(),
+                /* maxX= */ anyInt(),
+                /* maxY= */ anyInt()
+        );
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+    public void testStopFling_whileMagnifyingAndFlinging_stops() throws InterruptedException {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            stopFling_whileMagnifyingAndFlinging_stops(i);
+            resetMockWindowManager();
+        }
+    }
+
+    private void stopFling_whileMagnifyingAndFlinging_stops(int displayId)
+            throws InterruptedException {
+        register(displayId);
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        float scale = 2.0f;
+        PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
+        // First zoom in
+        assertTrue(mFullScreenMagnificationController
+                .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+                        SERVICE_ID_1));
+        mMessageCapturingHandler.sendAllMessages();
+
+        mFullScreenMagnificationController.startFling(displayId,
+                /* xPixelsPerSecond= */ 400f,
+                /* yPixelsPerSecond= */ 100f,
+                SERVICE_ID_1
+        );
+        mMessageCapturingHandler.sendAllMessages();
+
+        when(mMockTimeAnimator.isRunning()).thenReturn(true);
+
+        mFullScreenMagnificationController.cancelFling(displayId, SERVICE_ID_1);
+        mMessageCapturingHandler.sendAllMessages();
+
+        verify(mMockTimeAnimator).cancel();
+        // Can't verify forceFinished() because it's final
+//        verify(mMockScroller).forceFinished(eq(true));
+    }
+
+    @Test
     public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
         for (int i = 0; i < DISPLAY_COUNT; i++) {
             getIdOfLastServiceToChange_returnsCorrectValue(i);
@@ -736,7 +831,7 @@
 
         verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId),
                 any(Region.class), any(MagnificationConfig.class));
-        verify(mAnimationCallback).onResult(true);
+        verify(mAnimationCallback).onResult(eq(true), any());
     }
 
     @Test
@@ -772,7 +867,7 @@
         mMessageCapturingHandler.sendAllMessages();
 
         // Verify expected actions.
-        verify(mAnimationCallback).onResult(false);
+        verify(mAnimationCallback).onResult(eq(false), any());
         verify(mMockValueAnimator).start();
         verify(mMockValueAnimator).cancel();
 
@@ -782,7 +877,7 @@
         mStateListener.onAnimationEnd(mMockValueAnimator);
 
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
-        verify(lastAnimationCallback).onResult(true);
+        verify(lastAnimationCallback).onResult(eq(true), any());
     }
 
     @Test
@@ -1379,6 +1474,8 @@
     private void resetMockWindowManager() {
         Mockito.reset(mMockWindowManager);
         Mockito.reset(mMockThumbnail);
+        Mockito.reset(mMockScroller);
+        Mockito.reset(mMockTimeAnimator);
         initMockWindowManager();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 71d64cf..8c0d44c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
+import static org.mockito.AdditionalMatchers.gt;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
@@ -40,10 +41,13 @@
 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.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
@@ -65,6 +69,7 @@
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.widget.Scroller;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -79,6 +84,8 @@
 import com.android.server.testutils.TestHandler;
 import com.android.server.wm.WindowManagerInternal;
 
+import com.google.common.truth.Truth;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -186,6 +193,8 @@
     @Rule
     public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
 
+    private final Scroller mMockScroller = spy(new Scroller(mContext));
+
     private OffsettableClock mClock;
     private FullScreenMagnificationGestureHandler mMgh;
     private TestHandler mHandler;
@@ -218,18 +227,21 @@
         Settings.Secure.putFloatForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
                 UserHandle.USER_SYSTEM);
-        mFullScreenMagnificationController = new FullScreenMagnificationController(
-                mockController,
-                new Object(),
-                mMagnificationInfoChangedCallback,
-                new MagnificationScaleProvider(mContext),
-                () -> null,
-                ConcurrentUtils.DIRECT_EXECUTOR) {
-                @Override
-                public boolean magnificationRegionContains(int displayId, float x, float y) {
-                    return true;
-                }
-        };
+        mFullScreenMagnificationController =
+                new FullScreenMagnificationController(
+                        mockController,
+                        new Object(),
+                        mMagnificationInfoChangedCallback,
+                        new MagnificationScaleProvider(mContext),
+                        () -> null,
+                        ConcurrentUtils.DIRECT_EXECUTOR,
+                        () -> mMockScroller,
+                        TimeAnimator::new) {
+                    @Override
+                    public boolean magnificationRegionContains(int displayId, float x, float y) {
+                        return true;
+                    }
+                };
 
         doAnswer((Answer<Void>) invocationOnMock -> {
             Object[] args = invocationOnMock.getArguments();
@@ -263,11 +275,20 @@
     @NonNull
     private FullScreenMagnificationGestureHandler newInstance(boolean detectSingleFingerTripleTap,
             boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger) {
-        FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
-                mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback,
-                detectSingleFingerTripleTap, detectTwoFingerTripleTap, detectShortcutTrigger,
-                mWindowMagnificationPromptController, DISPLAY_0,
-                mMockFullScreenMagnificationVibrationHelper, mMockMagnificationLogger);
+        FullScreenMagnificationGestureHandler h =
+                new FullScreenMagnificationGestureHandler(
+                        mContext,
+                        mFullScreenMagnificationController,
+                        mMockTraceManager,
+                        mMockCallback,
+                        detectSingleFingerTripleTap,
+                        detectTwoFingerTripleTap,
+                        detectShortcutTrigger,
+                        mWindowMagnificationPromptController,
+                        DISPLAY_0,
+                        mMockFullScreenMagnificationVibrationHelper,
+                        mMockMagnificationLogger,
+                        ViewConfiguration.get(mContext));
         if (isWatch()) {
             h.setSinglePanningEnabled(true);
         } else {
@@ -724,7 +745,7 @@
         //The minimum movement to transit to panningState.
         final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
         pointer2.offset(sWipeMinDistance + 1, 0);
-        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
         fastForward(ViewConfiguration.getTapTimeout());
         assertIn(STATE_PANNING);
 
@@ -743,7 +764,7 @@
         //The minimum movement to transit to panningState.
         final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
         pointer2.offset(sWipeMinDistance + 1, 0);
-        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
         fastForward(ViewConfiguration.getTapTimeout());
         assertIn(STATE_PANNING);
 
@@ -762,7 +783,7 @@
         //The minimum movement to transit to panningState.
         final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
         pointer2.offset(sWipeMinDistance + 1, 0);
-        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
         assertIn(STATE_PANNING);
 
         returnToNormalFrom(STATE_PANNING);
@@ -780,7 +801,7 @@
         //The minimum movement to transit to panningState.
         final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
         pointer2.offset(sWipeMinDistance + 1, 0);
-        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
         assertIn(STATE_PANNING);
 
         returnToNormalFrom(STATE_PANNING);
@@ -972,6 +993,198 @@
     }
 
     @Test
+    public void singleFinger_testScrollAfterMagnified_startsFling() {
+        assumeTrue(mMgh.mIsSinglePanningEnabled);
+        goFromStateIdleTo(STATE_ACTIVATED);
+
+        swipeAndHold();
+        fastForward(20);
+        swipe(DEFAULT_POINT, new PointF(DEFAULT_X * 2, DEFAULT_Y * 2), /* durationMs= */ 20);
+
+        verify(mMockScroller).fling(
+                /* startX= */ anyInt(),
+                /* startY= */ anyInt(),
+                // The system fling velocity is configurable and hard to test across devices, so as
+                // long as there is some fling velocity, we are happy.
+                /* velocityX= */ gt(1000),
+                /* velocityY= */ gt(1000),
+                /* minX= */ anyInt(),
+                /* minY= */ anyInt(),
+                /* maxX= */ anyInt(),
+                /* maxY= */ anyInt()
+        );
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+    public void testTwoFingerPanDiagonalAfterMagnified_doesNotFlingXY()
+            throws InterruptedException {
+        goFromStateIdleTo(STATE_ACTIVATED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[]{pointer1, pointer2}, 1));
+
+        // first move triggers the panning state
+        pointer1.offset(100, 100);
+        pointer2.offset(100, 100);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+
+        // second move actually pans
+        pointer1.offset(100, 100);
+        pointer2.offset(100, 100);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+        pointer1.offset(100, 100);
+        pointer2.offset(100, 100);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+
+        assertIn(STATE_PANNING);
+        mHandler.timeAdvance();
+        returnToNormalFrom(STATE_PANNING);
+
+        mHandler.timeAdvance();
+
+        verifyNoMoreInteractions(mMockScroller);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+    public void testTwoFingerPanDiagonalAfterMagnified_startsFlingXY()
+            throws InterruptedException {
+        goFromStateIdleTo(STATE_ACTIVATED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+        // first move triggers the panning state
+        pointer1.offset(100, 100);
+        pointer2.offset(100, 100);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+        // second move actually pans
+        pointer1.offset(100, 100);
+        pointer2.offset(100, 100);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+        pointer1.offset(100, 100);
+        pointer2.offset(100, 100);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+        assertIn(STATE_PANNING);
+        mHandler.timeAdvance();
+        returnToNormalFrom(STATE_PANNING);
+
+        mHandler.timeAdvance();
+
+        verify(mMockScroller).fling(
+                /* startX= */ anyInt(),
+                /* startY= */ anyInt(),
+                // The system fling velocity is configurable and hard to test across devices, so as
+                // long as there is some fling velocity, we are happy.
+                /* velocityX= */ gt(1000),
+                /* velocityY= */ gt(1000),
+                /* minX= */ anyInt(),
+                /* minY= */ anyInt(),
+                /* maxX= */ anyInt(),
+                /* maxY= */ anyInt()
+        );
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+    public void testTwoFingerPanRightAfterMagnified_startsFlingXOnly()
+            throws InterruptedException {
+        goFromStateIdleTo(STATE_ACTIVATED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+        // first move triggers the panning state
+        pointer1.offset(100, 0);
+        pointer2.offset(100, 0);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+        // second move actually pans
+        pointer1.offset(100, 0);
+        pointer2.offset(100, 0);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+        pointer1.offset(100, 0);
+        pointer2.offset(100, 0);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+        assertIn(STATE_PANNING);
+        mHandler.timeAdvance();
+        returnToNormalFrom(STATE_PANNING);
+
+        mHandler.timeAdvance();
+
+        verify(mMockScroller).fling(
+                /* startX= */ anyInt(),
+                /* startY= */ anyInt(),
+                // The system fling velocity is configurable and hard to test across devices, so as
+                // long as there is some fling velocity, we are happy.
+                /* velocityX= */ gt(100),
+                /* velocityY= */ eq(0),
+                /* minX= */ anyInt(),
+                /* minY= */ anyInt(),
+                /* maxX= */ anyInt(),
+                /* maxY= */ anyInt()
+        );
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+    public void testDownEvent_cancelsFling()
+            throws InterruptedException {
+        goFromStateIdleTo(STATE_ACTIVATED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+        // first move triggers the panning state
+        pointer1.offset(100, 0);
+        pointer2.offset(100, 0);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+        // second move actually pans
+        pointer1.offset(100, 0);
+        pointer2.offset(100, 0);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+        pointer1.offset(100, 0);
+        pointer2.offset(100, 0);
+        fastForward(20);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+        assertIn(STATE_PANNING);
+        mHandler.timeAdvance();
+        returnToNormalFrom(STATE_PANNING);
+
+        mHandler.timeAdvance();
+
+        send(downEvent());
+        mHandler.timeAdvance();
+
+        verify(mMockScroller).forceFinished(eq(true));
+    }
+
+    @Test
     public void testShortcutTriggered_invokeShowWindowPromptAction() {
         goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
 
@@ -1397,8 +1610,11 @@
         send(upEvent());
     }
 
-    private void swipe(PointF start, PointF end) {
-        swipeAndHold(start, end);
+    private void swipe(PointF start, PointF end, int durationMs) {
+        var mid = new PointF(start.x + (end.x - start.x) / 2f, start.y + (end.y - start.y) / 2f);
+        swipeAndHold(start, mid);
+        fastForward(durationMs);
+        send(moveEvent(end.x - start.x / 10f, end.y - start.y / 10f));
         send(upEvent(end.x, end.y));
     }
 
@@ -1491,9 +1707,18 @@
 
 
     private MotionEvent pointerEvent(int action, float x, float y) {
-        return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)}, 1);
+        return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)},
+                (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN) ? 1 : 0);
     }
 
+    /**
+     * Create a pointer event simulating the given pointer positions.
+     *
+     * @param action the action
+     * @param pointersPosition positions of the pointers
+     * @param changedIndex the index of the pointer associated with the ACTION_POINTER_UP or
+     *                     ACTION_POINTER_DOWN action. Must be 0 for all other actions.
+     */
     private MotionEvent pointerEvent(int action, PointF[] pointersPosition, int changedIndex) {
         final MotionEvent.PointerProperties[] PointerPropertiesArray =
                 new MotionEvent.PointerProperties[pointersPosition.length];
@@ -1513,12 +1738,12 @@
             pointerCoordsArray[i] = pointerCoords;
         }
 
-        action += (changedIndex << ACTION_POINTER_INDEX_SHIFT);
+        var actionWithPointer = action | (changedIndex << ACTION_POINTER_INDEX_SHIFT);
 
-        return MotionEvent.obtain(
+        var event = MotionEvent.obtain(
                 /* downTime */ mClock.now(),
                 /* eventTime */ mClock.now(),
-                /* action */ action,
+                /* action */ actionWithPointer,
                 /* pointerCount */ pointersPosition.length,
                 /* pointerProperties */ PointerPropertiesArray,
                 /* pointerCoords */ pointerCoordsArray,
@@ -1530,6 +1755,14 @@
                 /* edgeFlags */ 0,
                 /* source */ InputDevice.SOURCE_TOUCHSCREEN,
                 /* flags */ 0);
+
+        Truth.assertThat(event.getActionIndex()).isEqualTo(changedIndex);
+        Truth.assertThat(event.getActionMasked()).isEqualTo(action);
+        if (action == ACTION_DOWN) {
+            Truth.assertThat(changedIndex).isEqualTo(0);
+        }
+
+        return event;
     }
 
 
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 28d07f9..cd904eb 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
@@ -42,6 +42,7 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.MagnificationConfig;
+import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -59,6 +60,7 @@
 import android.view.DisplayInfo;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
+import android.widget.Scroller;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
@@ -119,6 +121,8 @@
     @Mock
     private ValueAnimator mValueAnimator;
     @Mock
+    private TimeAnimator mTimeAnimator;
+    @Mock
     private MessageCapturingHandler mMessageCapturingHandler;
 
     private FullScreenMagnificationController mScreenMagnificationController;
@@ -195,14 +199,17 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
 
-        mScreenMagnificationController = spy(new FullScreenMagnificationController(
-                mControllerCtx,
-                new Object(),
-                mScreenMagnificationInfoChangedCallbackDelegate,
-                mScaleProvider,
-                () -> null,
-                ConcurrentUtils.DIRECT_EXECUTOR
-        ));
+        mScreenMagnificationController =
+                spy(
+                        new FullScreenMagnificationController(
+                                mControllerCtx,
+                                new Object(),
+                                mScreenMagnificationInfoChangedCallbackDelegate,
+                                mScaleProvider,
+                                () -> null,
+                                ConcurrentUtils.DIRECT_EXECUTOR,
+                                () -> new Scroller(mContext),
+                                () -> mTimeAnimator));
         mScreenMagnificationController.register(TEST_DISPLAY);
 
         mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 408442b..35ad55c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -74,7 +74,9 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.keymaster.HardwareAuthenticatorType;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
@@ -83,6 +85,8 @@
 import android.security.KeyStore;
 import android.security.authorization.IKeystoreAuthorization;
 import android.service.gatekeeper.IGateKeeperService;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
@@ -100,6 +104,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -110,6 +115,8 @@
 
 @Presubmit
 @SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
 public class BiometricServiceTest {
 
     @Rule
@@ -171,6 +178,8 @@
     private UserManager mUserManager;
     @Mock
     private BiometricCameraManager mBiometricCameraManager;
+    @Mock
+    private BiometricHandlerProvider mBiometricHandlerProvider;
 
     @Mock
     private IKeystoreAuthorization mKeystoreAuthService;
@@ -235,6 +244,14 @@
         when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
         when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
 
+        if (com.android.server.biometrics.Flags.deHidl()) {
+            when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                    new Handler(TestableLooper.get(this).getLooper()));
+        } else {
+            when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                    new Handler(Looper.getMainLooper()));
+        }
+
         final String[] config = {
                 "0:2:15",  // ID0:Fingerprint:Strong
                 "1:8:15",  // ID1:Face:Strong
@@ -312,7 +329,7 @@
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(false);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -333,7 +350,7 @@
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(true);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -360,7 +377,7 @@
     @Test
     public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
             Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -377,7 +394,7 @@
     public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
         mBiometricService.mImpl.registerAuthenticator(0 /* id */,
                 TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -451,7 +468,7 @@
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
         mBiometricService.mImpl.registerAuthenticator(0 /* id */,
                 TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -1374,7 +1391,7 @@
 
     @Test
     public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         // Credential requested but not set up
@@ -1428,7 +1445,7 @@
 
     @Test
     public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         // When only biometric is requested
@@ -1515,7 +1532,7 @@
 
     @Test
     public void testRegisterAuthenticator_updatesStrengths() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         verify(mBiometricService.mBiometricStrengthController).startListening();
@@ -1533,7 +1550,7 @@
 
     @Test
     public void testWithDowngradedAuthenticator() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         final int testId = 0;
@@ -1639,7 +1656,7 @@
 
     @Test(expected = IllegalStateException.class)
     public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         mBiometricService.mImpl.registerAuthenticator(
@@ -1653,7 +1670,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
             throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         mBiometricService.mImpl.registerAuthenticator(
@@ -1665,7 +1682,7 @@
     @Test
     public void testRegistrationHappyPath_isOk() throws Exception {
         // This is being tested in many of the other cases, but here's the base case.
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         for (String s : mInjector.getConfiguration(null)) {
@@ -1751,7 +1768,7 @@
         final IBiometricEnabledOnKeyguardCallback callback =
                 mock(IBiometricEnabledOnKeyguardCallback.class);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
 
         when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
         when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id))
@@ -1775,7 +1792,7 @@
             throws RemoteException {
         mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
     }
 
@@ -1799,7 +1816,7 @@
         when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
                 .thenReturn(expectedResult);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
 
         final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
                 Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
@@ -1822,7 +1839,7 @@
 
     // TODO: Reconcile the registration strength with the injector
     private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1855,7 +1872,7 @@
     // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
     // all tests.
     private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1993,8 +2010,12 @@
         return requestWrapper.eligibleSensors.get(0).getCookie();
     }
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    private void waitForIdle() {
+        if (com.android.server.biometrics.Flags.deHidl()) {
+            TestableLooper.get(this).processAllMessages();
+        } else {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
     }
 
     private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 7648bd17..9eca93e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,19 +24,28 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.HidlFaceSensorConfig;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -49,18 +58,23 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -92,6 +106,14 @@
     private BiometricStateCallback mBiometricStateCallback;
     @Mock
     private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
+    private BiometricHandlerProvider mBiometricHandlerProvider;
+    @Mock
+    private Handler mBiometricCallbackHandler;
+    @Mock
+    private BiometricScheduler<IFace, ISession> mScheduler;
+    @Mock
+    AuthSessionCoordinator mAuthSessionCoordinator;
 
     private final TestLooper mLooper = new TestLooper();
     private SensorProps[] mSensorProps;
@@ -109,6 +131,16 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
                 .thenReturn(FRR_THRESHOLD);
+        when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                mBiometricCallbackHandler);
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        if (Flags.deHidl()) {
+            when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+                    mLooper.getLooper()));
+        } else {
+            when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+                    Looper.getMainLooper()));
+        }
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
@@ -123,7 +155,7 @@
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
                 mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
-                mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
+                mBiometricContext, mDaemon, mBiometricHandlerProvider,
                 false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
@@ -159,8 +191,7 @@
         mFaceProvider = new FaceProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
                 mLockoutResetDispatcher, mBiometricContext, mDaemon,
-                new Handler(mLooper.getLooper()),
-                true /* resetLockoutRequiresChallenge */,
+                mBiometricHandlerProvider, true /* resetLockoutRequiresChallenge */,
                 true /* testHalEnabled */);
 
         assertThat(mFaceProvider.mFaceSensors.get(faceId)
@@ -215,6 +246,54 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testAuthenticateCallbackHandler() {
+        waitForIdle();
+
+        mFaceProvider.mFaceSensors.get(0).setScheduler(mScheduler);
+        mFaceProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+                0 /* cookie */, new ClientMonitorCallbackConverter(
+                        new IBiometricSensorReceiver.Default()),
+                new FaceAuthenticateOptions.Builder()
+                        .setSensorId(0)
+                        .build(),
+                false /* restricted */, 1 /* statsClient */,
+                true /* allowBackgroundAuthentication */);
+
+        waitForIdle();
+
+        ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+                ClientMonitorCallback.class);
+        ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+                BaseClientMonitor.class);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+                Message.class);
+
+        verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+                callbackArgumentCaptor.capture());
+
+        BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+        ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+        callback.onClientStarted(client);
+
+        verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+        callback.onClientFinished(client, true /* success */);
+
+        verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+                messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+                anyBoolean());
+    }
+
     private void waitForIdle() {
         if (Flags.deHidl()) {
             mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f96d9e8..9cdaec6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -18,6 +18,7 @@
 
 import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 
 import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
 
@@ -517,6 +518,16 @@
     }
 
     @Test
+    public void testAuthenticationStateListeners_onAuthenticationAcquired()
+            throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAcquired(FINGERPRINT_ACQUIRED_START, 0);
+
+        verify(mAuthenticationStateListeners).onAuthenticationAcquired(any(), anyInt(), anyInt());
+    }
+
+    @Test
     public void testAuthenticationStateListeners_onAuthenticationSucceeded()
             throws RemoteException {
         mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 258be57..0a35037 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,21 +24,31 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -50,11 +60,16 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -62,6 +77,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -93,6 +109,14 @@
     private BiometricStateCallback mBiometricStateCallback;
     @Mock
     private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricHandlerProvider mBiometricHandlerProvider;
+    @Mock
+    private Handler mBiometricCallbackHandler;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private BiometricScheduler<IFingerprint, ISession> mScheduler;
 
     private final TestLooper mLooper = new TestLooper();
 
@@ -109,6 +133,16 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
         when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                mBiometricCallbackHandler);
+        if (Flags.deHidl()) {
+            when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+                    new Handler(mLooper.getLooper()));
+        } else {
+            when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+                    new Handler(Looper.getMainLooper()));
+        }
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
@@ -126,9 +160,8 @@
         mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
-                mDaemon, new Handler(mLooper.getLooper()),
-                false /* resetLockoutRequiresHardwareAuthToken */,
-                true /* testHalEnabled */);
+                mDaemon, mBiometricHandlerProvider,
+                false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */);
     }
 
     @Test
@@ -160,7 +193,7 @@
                 mBiometricStateCallback, mAuthenticationStateListeners,
                 hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
                 mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
-                new Handler(mLooper.getLooper()),
+                mBiometricHandlerProvider,
                 false /* resetLockoutRequiresHardwareAuthToken */,
                 true /* testHalEnabled */);
 
@@ -218,6 +251,56 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleAuthenticate() {
+        waitForIdle();
+
+        mFingerprintProvider.mFingerprintSensors.get(0).setScheduler(mScheduler);
+        mFingerprintProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+                0 /* cookie */, new ClientMonitorCallbackConverter(
+                        new IBiometricSensorReceiver.Default()),
+                new FingerprintAuthenticateOptions.Builder()
+                        .setSensorId(0)
+                        .build(),
+                false /* restricted */, 1 /* statsClient */,
+                true /* allowBackgroundAuthentication */);
+
+        waitForIdle();
+
+        ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+                ClientMonitorCallback.class);
+        ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+                BaseClientMonitor.class);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+                Message.class);
+
+        verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+                callbackArgumentCaptor.capture());
+
+        BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+        ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+        callback.onClientStarted(client);
+
+        verify(mBiometricStateCallback).onClientStarted(eq(client));
+        verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+        callback.onClientFinished(client, true /* success */);
+
+        verify(mBiometricStateCallback).onClientFinished(eq(client), eq(true /* success */));
+        verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+                messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+                anyBoolean());
+    }
+
     private void waitForIdle() {
         if (Flags.deHidl()) {
             mLooper.dispatchAll();
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 7dcfc88..fa39364 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -30,6 +30,7 @@
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertThrows;
 
+import android.annotation.NonNull;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.hardware.devicestate.IDeviceStateManagerCallback;
@@ -52,6 +53,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Optional;
@@ -959,6 +961,10 @@
             }
             onComplete.run();
         }
+
+        @Override
+        public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        }
     }
 
     private static final class TestDeviceStateProvider implements DeviceStateProvider {
@@ -1001,6 +1007,10 @@
         public void setState(int identifier) {
             mListener.onStateChanged(identifier);
         }
+
+        @Override
+        public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
+        }
     }
 
     private static final class TestDeviceStateManagerCallback extends
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index e7da26e..98789ac 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -519,11 +519,12 @@
                 eq(AudioManager.ADJUST_MUTE), anyInt());
         clearInvocations(mAudioManager);
 
-        // New volume only: sets volume only
+        // New volume only: sets both volume and mute.
+        // Volume changes can affect mute status; we need to set mute afterwards to undo this.
         receiveReportAudioStatus(32, true);
         verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
                 anyInt());
-        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
                 eq(AudioManager.ADJUST_MUTE), anyInt());
         clearInvocations(mAudioManager);
 
@@ -536,17 +537,17 @@
         clearInvocations(mAudioManager);
 
         // Repeat of earlier message: sets neither volume nor mute
-        // Exception: On TV, volume is set to ensure that UI is shown
+        // Exception: On TV, mute is set to ensure that UI is shown
         receiveReportAudioStatus(32, false);
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(32), anyInt());
         if (getDeviceType() == HdmiDeviceInfo.DEVICE_TV) {
-            verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
-                    anyInt());
+            verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                    eq(AudioManager.ADJUST_UNMUTE), anyInt());
         } else {
-            verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
-                    anyInt());
+            verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                    eq(AudioManager.ADJUST_UNMUTE), anyInt());
         }
-        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
-                eq(AudioManager.ADJUST_UNMUTE), anyInt());
         clearInvocations(mAudioManager);
 
         // Volume not within range [0, 100]: sets neither volume nor mute
@@ -570,7 +571,8 @@
         receiveReportAudioStatus(32, false);
         verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
                 anyInt());
-        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+        // Update mute status because we updated volume
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
                 eq(AudioManager.ADJUST_UNMUTE), anyInt());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index a410702..9ad2652 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -174,11 +174,12 @@
                 eq(AudioManager.ADJUST_MUTE), anyInt());
         clearInvocations(mAudioManager);
 
-        // New volume only: sets volume only
+        // New volume only: sets both volume and mute.
+        // Volume changes can affect mute status; we need to set mute afterwards to undo this.
         receiveReportAudioStatus(32, true);
         verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
                 anyInt());
-        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
                 eq(AudioManager.ADJUST_MUTE), anyInt());
         clearInvocations(mAudioManager);
 
@@ -190,11 +191,11 @@
                 eq(AudioManager.ADJUST_UNMUTE), anyInt());
         clearInvocations(mAudioManager);
 
-        // Repeat of earlier message: sets volume only (to ensure volume UI is shown)
+        // Repeat of earlier message: sets mute only (to ensure volume UI is shown)
         receiveReportAudioStatus(32, false);
-        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
                 anyInt());
-        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
                 eq(AudioManager.ADJUST_UNMUTE), anyInt());
         clearInvocations(mAudioManager);
 
@@ -392,18 +393,18 @@
                 INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()));
         mTestLooper.dispatchAll();
 
-        // HdmiControlService calls setStreamVolume to trigger volume UI
-        verify(mAudioManager).setStreamVolume(
+        // HdmiControlService calls adjustStreamVolume to trigger volume UI
+        verify(mAudioManager).adjustStreamVolume(
+                eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE),
+                eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+        // setStreamVolume is not called because volume didn't change,
+        // and adjustStreamVolume is sufficient to show volume UI
+        verify(mAudioManager, never()).setStreamVolume(
                 eq(AudioManager.STREAM_MUSIC),
                 // Volume level is rescaled to the max volume of STREAM_MUSIC
                 eq(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume()
                         * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
                 eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
-        // adjustStreamVolume is not called because mute status didn't change,
-        // and setStreamVolume is sufficient to show volume UI
-        verify(mAudioManager, never()).adjustStreamVolume(
-                eq(AudioManager.STREAM_MUSIC),
-                eq(AudioManager.ADJUST_UNMUTE),
-                eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 213e05e..2b07d33 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
 import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -39,8 +40,6 @@
 
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Random;
 
 public class PendingJobQueueTest {
@@ -68,7 +67,7 @@
 
     @Test
     public void testAdd() {
-        List<JobStatus> jobs = new ArrayList<>();
+        ArraySet<JobStatus> jobs = new ArraySet<>();
         jobs.add(createJobStatus("testAdd", createJobInfo(1), 1));
         jobs.add(createJobStatus("testAdd", createJobInfo(2), 2));
         jobs.add(createJobStatus("testAdd", createJobInfo(3).setExpedited(true), 3));
@@ -77,7 +76,7 @@
 
         PendingJobQueue jobQueue = new PendingJobQueue();
         for (int i = 0; i < jobs.size(); ++i) {
-            jobQueue.add(jobs.get(i));
+            jobQueue.add(jobs.valueAt(i));
             assertEquals(i + 1, jobQueue.size());
         }
 
@@ -90,7 +89,7 @@
 
     @Test
     public void testAddAll() {
-        List<JobStatus> jobs = new ArrayList<>();
+        ArraySet<JobStatus> jobs = new ArraySet<>();
         jobs.add(createJobStatus("testAddAll", createJobInfo(1), 1));
         jobs.add(createJobStatus("testAddAll", createJobInfo(2), 2));
         jobs.add(createJobStatus("testAddAll", createJobInfo(3).setExpedited(true), 3));
@@ -110,7 +109,7 @@
 
     @Test
     public void testClear() {
-        List<JobStatus> jobs = new ArrayList<>();
+        ArraySet<JobStatus> jobs = new ArraySet<>();
         jobs.add(createJobStatus("testClear", createJobInfo(1), 1));
         jobs.add(createJobStatus("testClear", createJobInfo(2), 2));
         jobs.add(createJobStatus("testClear", createJobInfo(3).setExpedited(true), 3));
@@ -179,7 +178,7 @@
 
     @Test
     public void testRemove() {
-        List<JobStatus> jobs = new ArrayList<>();
+        ArraySet<JobStatus> jobs = new ArraySet<>();
         jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
         jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
         jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -192,8 +191,8 @@
         ArraySet<JobStatus> removed = new ArraySet<>();
         JobStatus job;
         for (int i = 0; i < jobs.size(); ++i) {
-            jobQueue.remove(jobs.get(i));
-            removed.add(jobs.get(i));
+            jobQueue.remove(jobs.valueAt(i));
+            removed.add(jobs.valueAt(i));
 
             assertEquals(jobs.size() - i - 1, jobQueue.size());
 
@@ -209,7 +208,7 @@
 
     @Test
     public void testRemove_duringIteration() {
-        List<JobStatus> jobs = new ArrayList<>();
+        ArraySet<JobStatus> jobs = new ArraySet<>();
         jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
         jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
         jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -234,7 +233,7 @@
 
     @Test
     public void testRemove_outOfOrder() {
-        List<JobStatus> jobs = new ArrayList<>();
+        ArraySet<JobStatus> jobs = new ArraySet<>();
         JobStatus job1 = createJobStatus("testRemove", createJobInfo(1), 1);
         JobStatus job2 = createJobStatus("testRemove", createJobInfo(2), 1);
         JobStatus job3 = createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 1);
@@ -269,8 +268,8 @@
             Log.d(TAG, testJobToString(job));
         }
         for (int i = 0; i < jobs.size(); ++i) {
-            jobQueue.remove(jobs.get(i));
-            removed.add(jobs.get(i));
+            jobQueue.remove(jobs.valueAt(i));
+            removed.add(jobs.valueAt(i));
 
             assertEquals(jobs.size() - i - 1, jobQueue.size());
 
@@ -294,8 +293,8 @@
 
         removed.clear();
         for (int i = 0; i < jobs.size(); ++i) {
-            jobQueue.remove(jobs.get(i));
-            removed.add(jobs.get(i));
+            jobQueue.remove(jobs.valueAt(i));
+            removed.add(jobs.valueAt(i));
 
             assertEquals(jobs.size() - i - 1, jobQueue.size());
 
@@ -319,8 +318,8 @@
 
         removed.clear();
         for (int i = 0; i < jobs.size(); ++i) {
-            jobQueue.remove(jobs.get(i));
-            removed.add(jobs.get(i));
+            jobQueue.remove(jobs.valueAt(i));
+            removed.add(jobs.valueAt(i));
 
             assertEquals(jobs.size() - i - 1, jobQueue.size());
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5081198..7053597 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -39,7 +39,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.PropertyInvalidatedCache;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -49,8 +48,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.widget.ILockSettingsStateListener;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsStateListener;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
@@ -412,7 +411,7 @@
         mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
         final LockscreenCredential password = newPassword("password");
         setCredential(PRIMARY_USER_ID, password);
-        final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+        final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
         mLocalService.registerLockSettingsStateListener(listener);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
@@ -429,7 +428,7 @@
         final LockscreenCredential password = newPassword("password");
         setCredential(PRIMARY_USER_ID, password);
         final LockscreenCredential badPassword = newPassword("badPassword");
-        final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+        final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
         mLocalService.registerLockSettingsStateListener(listener);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
@@ -445,7 +444,7 @@
         final LockscreenCredential password = newPassword("password");
         setCredential(PRIMARY_USER_ID, password);
         final LockscreenCredential badPassword = newPassword("badPassword");
-        final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+        final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
 
         mLocalService.registerLockSettingsStateListener(listener);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
@@ -599,12 +598,4 @@
             assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
         }
     }
-
-    private ILockSettingsStateListener mockLockSettingsStateListener() {
-        ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class);
-        IBinder binder = mock(IBinder.class);
-        when(binder.isBinderAlive()).thenReturn(true);
-        when(listener.asBinder()).thenReturn(binder);
-        return listener;
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/FileHashCacheTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/FileHashCacheTests.java
new file mode 100644
index 0000000..5df7a5e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/FileHashCacheTests.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.server.net.watchlist;
+
+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.junit.Assert.fail;
+
+import android.annotation.NonNull;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.system.Os;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * atest frameworks-services -c com.android.server.net.watchlist.FileHashCacheTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FileHashCacheTests {
+
+    private static final String APK_A = "A.apk";
+    private static final String APK_B = "B.apk";
+    private static final String APK_A_CONTENT = "AAA";
+    private static final String APK_A_ALT_CONTENT = "AAA_ALT";
+    private static final String APK_B_CONTENT = "BBB";
+
+    private static final String PERSIST_FILE_NAME_FOR_TEST = "file_hash_cache";
+
+    // Sha256 of "AAA"
+    private static final String APK_A_CONTENT_HASH =
+            "CB1AD2119D8FAFB69566510EE712661F9F14B83385006EF92AEC47F523A38358";
+    // Sha256 of "AAA_ALT"
+    private static final String APK_A_ALT_CONTENT_HASH =
+            "2AB726E3C5B316F4C7507BFCCC3861F0473523D572E0C62BA21601C20693AEF0";
+    // Sha256 of "BBB"
+    private static final String APK_B_CONTENT_HASH =
+            "DCDB704109A454784B81229D2B05F368692E758BFA33CB61D04C1B93791B0273";
+
+    @Before
+    public void setUp() throws Exception {
+        final File persistFile = getFile(PERSIST_FILE_NAME_FOR_TEST);
+        persistFile.delete();
+        FileHashCache.sPersistFileName = persistFile.getAbsolutePath();
+        getFile(APK_A).delete();
+        getFile(APK_B).delete();
+        FileHashCache.sSaveDeferredDelayMillis = 0;
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testFileHashCache_generic() throws Exception {
+        final File apkA = getFile(APK_A);
+        final File apkB = getFile(APK_B);
+
+        Looper.prepare();
+        FileHashCache fileHashCache = new FileHashCache(new InlineHandler());
+
+        assertFalse(getFile(PERSIST_FILE_NAME_FOR_TEST).exists());
+
+        // No hash for non-existing files.
+        assertNull("Found existing entry in the cache",
+                fileHashCache.getSha256HashFromCache(apkA));
+        assertNull("Found existing entry in the cache",
+                fileHashCache.getSha256HashFromCache(apkB));
+        try {
+            fileHashCache.getSha256Hash(apkA);
+            fail("Not reached");
+        } catch (IOException e) { }
+        try {
+            fileHashCache.getSha256Hash(apkB);
+            fail("Not reached");
+        } catch (IOException e) { }
+
+        assertFalse(getFile(PERSIST_FILE_NAME_FOR_TEST).exists());
+        FileUtils.stringToFile(apkA, APK_A_CONTENT);
+        FileUtils.stringToFile(apkB, APK_B_CONTENT);
+
+        assertEquals(APK_A_CONTENT_HASH, HexDump.toHexString(fileHashCache.getSha256Hash(apkA)));
+        assertTrue(getFile(PERSIST_FILE_NAME_FOR_TEST).exists());
+        assertEquals(APK_B_CONTENT_HASH, HexDump.toHexString(fileHashCache.getSha256Hash(apkB)));
+        assertEquals(APK_A_CONTENT_HASH,
+                HexDump.toHexString(fileHashCache.getSha256HashFromCache(apkA)));
+        assertEquals(APK_B_CONTENT_HASH,
+                HexDump.toHexString(fileHashCache.getSha256HashFromCache(apkB)));
+
+        // Recreate handler. It should read persistent state.
+        fileHashCache = new FileHashCache(new InlineHandler());
+        assertEquals(APK_A_CONTENT_HASH,
+                HexDump.toHexString(fileHashCache.getSha256HashFromCache(apkA)));
+        assertEquals(APK_B_CONTENT_HASH,
+                HexDump.toHexString(fileHashCache.getSha256HashFromCache(apkB)));
+
+        // Modify one APK. Cache entry should be invalidated. Make sure that FS timestamp resolution
+        // allows us to detect update.
+        final long before = Os.stat(apkA.getAbsolutePath()).st_ctime;
+        do {
+            FileUtils.stringToFile(apkA, APK_A_ALT_CONTENT);
+        } while (android.system.Os.stat(apkA.getAbsolutePath()).st_ctime == before);
+
+        assertNull("Found stale entry in the cache", fileHashCache.getSha256HashFromCache(apkA));
+        assertEquals(APK_A_ALT_CONTENT_HASH,
+                HexDump.toHexString(fileHashCache.getSha256Hash(apkA)));
+    }
+
+    // Helper handler that executes tasks inline in context of current thread if time is good for
+    // this.
+    private static class InlineHandler extends Handler {
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            if (SystemClock.uptimeMillis() >= uptimeMillis && getLooper().isCurrentThread()) {
+                dispatchMessage(msg);
+                return true;
+            }
+            return super.sendMessageAtTime(msg, uptimeMillis);
+        }
+    }
+
+    private File getFile(@NonNull String name) {
+        return new File(InstrumentationRegistry.getContext().getFilesDir(), name);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 1c6d36b..ea84eb2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -36,6 +36,7 @@
 import android.os.IDumpstateListener;
 import android.os.Process;
 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;
@@ -48,6 +49,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;
@@ -118,6 +120,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
     public void testBugreportFileManagerFileExists() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
@@ -137,6 +140,7 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+    @Ignore
     public void testBugreportFileManagerKeepFilesOnRetrieval() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
@@ -150,6 +154,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
     public void testBugreportFileManagerMultipleFiles() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index c81fbb4..cee7387 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@
 import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.SparseArray;
 
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -109,6 +111,8 @@
     private IApplicationThread mIApplicationThread;
 
     private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@
         mUserEnabled.put(PRIMARY_USER, true);
         mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
         mUserEnabled.put(SECONDARY_USER, true);
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
 
-        when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+        when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
                 invocation -> {
                     List<Integer> users = new ArrayList<>();
                     final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index 39cc653..6decf43 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -114,16 +114,19 @@
         final String userType1 = USER_TYPE_PROFILE_MANAGED;
 
         // System user should still have no userType1 profile so getProfileIds should be empty.
-        int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1, false);
+        int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1,
+                false, /* excludeHidden */ false);
         assertEquals("System user should have no managed profiles", 0, users.length);
 
         // Secondary user should have one userType1 profile, so return just that.
-        users = mUserManagerService.getProfileIds(secondaryUser.id, userType1, false);
+        users = mUserManagerService.getProfileIds(secondaryUser.id, userType1,
+                false, /* excludeHidden */ false);
         assertEquals("Wrong number of profiles", 1, users.length);
         assertEquals("Wrong profile id", profile.id, users[0]);
 
         // The profile itself is a userType1 profile, so it should return just itself.
-        users = mUserManagerService.getProfileIds(profile.id, userType1, false);
+        users = mUserManagerService.getProfileIds(profile.id, userType1, false, /* excludeHidden */
+                false);
         assertEquals("Wrong number of profiles", 1, users.length);
         assertEquals("Wrong profile id", profile.id, users[0]);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8d8dc9c..3778a32 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -79,6 +79,7 @@
                 .setAlwaysVisible(false)
                 .setCrossProfileContentSharingStrategy(0)
                 .setProfileApiVisibility(34)
+                .setItemsRestrictedOnHomeScreen(false)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
@@ -97,6 +98,7 @@
         actualProps.setAlwaysVisible(true);
         actualProps.setCrossProfileContentSharingStrategy(1);
         actualProps.setProfileApiVisibility(36);
+        actualProps.setItemsRestrictedOnHomeScreen(true);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -144,6 +146,7 @@
                 .setAllowStoppingUserWithDelayedLocking(false)
                 .setAlwaysVisible(true)
                 .setProfileApiVisibility(110)
+                .setItemsRestrictedOnHomeScreen(false)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
@@ -154,6 +157,7 @@
         orig.setAuthAlwaysRequiredToDisableQuietMode(true);
         orig.setAllowStoppingUserWithDelayedLocking(true);
         orig.setAlwaysVisible(false);
+        orig.setItemsRestrictedOnHomeScreen(true);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
         for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -200,6 +204,8 @@
         assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
         assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking,
                 copy::getAllowStoppingUserWithDelayedLocking, exposeAll);
+        assertEqualGetterOrThrows(orig::areItemsRestrictedOnHomeScreen,
+                copy::areItemsRestrictedOnHomeScreen, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
         assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -283,5 +289,7 @@
         assertThat(expected.getCrossProfileContentSharingStrategy())
                 .isEqualTo(actual.getCrossProfileContentSharingStrategy());
         assertThat(expected.getProfileApiVisibility()).isEqualTo(actual.getProfileApiVisibility());
+        assertThat(expected.areItemsRestrictedOnHomeScreen())
+                .isEqualTo(actual.areItemsRestrictedOnHomeScreen());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 1ee604e..6cdbc74 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -102,7 +102,8 @@
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(true)
                 .setCrossProfileContentSharingStrategy(1)
-                .setProfileApiVisibility(34);
+                .setProfileApiVisibility(34)
+                .setItemsRestrictedOnHomeScreen(true);
 
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
@@ -186,6 +187,7 @@
         assertEquals(1, type.getDefaultUserPropertiesReference()
                 .getCrossProfileContentSharingStrategy());
         assertEquals(34, type.getDefaultUserPropertiesReference().getProfileApiVisibility());
+        assertTrue(type.getDefaultUserPropertiesReference().areItemsRestrictedOnHomeScreen());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -343,7 +345,8 @@
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(false)
                 .setCrossProfileContentSharingStrategy(1)
-                .setProfileApiVisibility(36);
+                .setProfileApiVisibility(36)
+                .setItemsRestrictedOnHomeScreen(false);
 
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
@@ -395,6 +398,7 @@
         assertEquals(1, aospType.getDefaultUserPropertiesReference()
                 .getCrossProfileContentSharingStrategy());
         assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
+        assertFalse(aospType.getDefaultUserPropertiesReference().areItemsRestrictedOnHomeScreen());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -452,6 +456,7 @@
         assertEquals(0, aospType.getDefaultUserPropertiesReference()
                 .getCrossProfileContentSharingStrategy());
         assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
+        assertTrue(aospType.getDefaultUserPropertiesReference().areItemsRestrictedOnHomeScreen());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index db561c4..9323b48 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -355,6 +355,8 @@
                 privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
         assertThrows(SecurityException.class,
                 privateProfileUserProperties::getProfileApiVisibility);
+        assertThrows(SecurityException.class,
+                privateProfileUserProperties::areItemsRestrictedOnHomeScreen);
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
index e075379..c0ea157 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -106,13 +106,13 @@
     }
 
     @Test
-    public void testRecord_changeUserForFile_throws() {
+    public void testRecord_changeUserForFile_ignored() {
         Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
         Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1");
 
         PackageDynamicCodeLoading info = makePackageDcl(entry1);
 
-        assertThrows(() -> record(info, entry2));
+        assertThat(record(info, entry2)).isFalse();
         assertHasEntries(info, entry1);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index ffb3bce..4ab9d3e 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -90,10 +90,12 @@
     private static final long[] DURATIONS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
-    private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
+    private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
         new WorkDuration(1L, 11L, 8L, 4L, 1L),
         new WorkDuration(2L, 13L, 8L, 6L, 2L),
         new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
+        new WorkDuration(2L, 13L, 0L, 6L, 2L),
+        new WorkDuration(2L, 13L, 8L, 0L, 2L),
     };
 
     @Mock private Context mContext;
@@ -609,9 +611,9 @@
                 .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
 
         a.updateTargetWorkDuration(100L);
-        a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+        a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
         verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
-                eq(WORK_DURATIONS_THREE));
+                eq(WORK_DURATIONS_FIVE));
 
         assertThrows(IllegalArgumentException.class, () -> {
             a.reportActualWorkDuration2(new WorkDuration[] {});
@@ -627,7 +629,7 @@
         });
 
         assertThrows(IllegalArgumentException.class, () -> {
-            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
+            a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 0L, 1L)});
         });
 
         assertThrows(IllegalArgumentException.class, () -> {
@@ -648,7 +650,7 @@
         latch.await();
 
         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
-        a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+        a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
         verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index aca96ad..eddff9ab 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -21,8 +21,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.testng.Assert.expectThrows;
 
+import android.content.pm.Signature;
+import android.content.pm.SignedPackage;
 import android.os.Build;
 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.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -68,6 +73,8 @@
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
+    @Rule public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mSysConfig = new SystemConfigTestClass();
@@ -696,6 +703,44 @@
         assertThat(mSysConfig.getSystemAppUpdateOwnerPackageName("com.foo")).isNull();
     }
 
+    /**
+     * Tests that SystemConfig::getEnhancedConfirmationTrustedInstallers correctly parses a list of
+     * SignedPackage objects.
+     */
+    @Test
+    @RequiresFlagsEnabled(
+            android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers()
+            throws IOException {
+        String pkgName = "com.example.app";
+        String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
+                + "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
+
+        byte[] certificateDigest = new Signature(certificateDigestStr.replace(":", ""))
+                .toByteArray();
+        String contents = "<config>"
+                + "<" + "enhanced-confirmation-trusted-installer" + " "
+                + "package=\"" + pkgName + "\""
+                + " sha256-cert-digest=\"" + certificateDigestStr + "\""
+                + "/>"
+                + "</config>";
+
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "enhanced-confirmation.xml", contents);
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        ArraySet<SignedPackage> actualTrustedInstallers =
+                mSysConfig.getEnhancedConfirmationTrustedInstallers();
+
+        assertThat(actualTrustedInstallers.size()).isEqualTo(1);
+        SignedPackage actual = actualTrustedInstallers.stream().findFirst().orElseThrow();
+        SignedPackage expected = new SignedPackage(pkgName, certificateDigest);
+
+        assertThat(actual.getCertificateDigest()).isEqualTo(expected.getCertificateDigest());
+        assertThat(actual.getPkgName()).isEqualTo(expected.getPkgName());
+        assertThat(actual).isEqualTo(expected);
+    }
+
     private void parseSharedLibraries(String contents) throws IOException {
         File folder = createTempSubfolder("permissions_folder");
         createTempFile(folder, "permissions.xml", contents);
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index b8dcecd..e27bb4c 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -320,4 +320,10 @@
         mWindowInfosReportedListeners.add(listener);
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setCanOccludePresentation(SurfaceControl sc,
+                boolean canOccludePresentation) {
+        return this;
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 839cf7c..c247c08 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -82,6 +82,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IpcDataCache;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
@@ -174,6 +175,11 @@
 
 
     @Before
+    public void disableProcessCaches() {
+        IpcDataCache.disableForTestMode();
+    }
+
+    @Before
     public void setUp() {
         // The AIDL stub will use PermissionEnforcer to check permission from the caller.
         mPermissionEnforcer = new FakePermissionEnforcer();
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 516fb4a..5eb76e3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,37 +15,56 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.COLOR_DEFAULT;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_CAN_COLORIZE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static com.android.server.notification.GroupHelper.BASE_FLAGS;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+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.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.annotation.SuppressLint;
 import android.app.Notification;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.server.UiServiceTestCase;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -59,23 +78,37 @@
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
 @RunWith(AndroidJUnit4.class)
 public class GroupHelperTest extends UiServiceTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     private @Mock GroupHelper.Callback mCallback;
+    private @Mock PackageManager mPackageManager;
 
     private final static int AUTOGROUP_AT_COUNT = 7;
     private GroupHelper mGroupHelper;
+    private @Mock Icon mSmallIcon;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mGroupHelper = new GroupHelper(AUTOGROUP_AT_COUNT, mCallback);
+        mGroupHelper = new GroupHelper(getContext(), mPackageManager, AUTOGROUP_AT_COUNT,
+                mCallback);
+
+        NotificationRecord r = mock(NotificationRecord.class);
+        StatusBarNotification sbn = getSbn("package", 0, "0", UserHandle.SYSTEM);
+        when(r.getNotification()).thenReturn(sbn.getNotification());
+        when(r.getSbn()).thenReturn(sbn);
+        when(mSmallIcon.sameAs(mSmallIcon)).thenReturn(true);
     }
 
     private StatusBarNotification getSbn(String pkg, int id, String tag,
-            UserHandle user, String groupKey) {
+            UserHandle user, String groupKey, Icon smallIcon, int iconColor) {
         Notification.Builder nb = new Notification.Builder(getContext(), "test_channel_id")
                 .setContentTitle("A")
-                .setWhen(1205);
+                .setWhen(1205)
+                .setSmallIcon(smallIcon)
+                .setColor(iconColor);
         if (groupKey != null) {
             nb.setGroup(groupKey);
         }
@@ -84,23 +117,32 @@
     }
 
     private StatusBarNotification getSbn(String pkg, int id, String tag,
+            UserHandle user, String groupKey) {
+        return getSbn(pkg, id, tag, user, groupKey, mSmallIcon, Notification.COLOR_DEFAULT);
+    }
+
+    private StatusBarNotification getSbn(String pkg, int id, String tag,
             UserHandle user) {
         return getSbn(pkg, id, tag, user, null);
     }
 
+    private NotificationAttributes getNotificationAttributes(int flags) {
+        return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT);
+    }
+
     @Test
     public void testGetAutogroupSummaryFlags_noChildren() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
 
         assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
     }
 
     @Test
     public void testGetAutogroupSummaryFlags_oneOngoing() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -108,10 +150,10 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_NO_CLEAR));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -119,10 +161,10 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_BUBBLE));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -130,11 +172,11 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_multipleOngoing() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT);
-        children.put("c", FLAG_BUBBLE);
-        children.put("d", FLAG_ONGOING_EVENT);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
+        children.put("d", getNotificationAttributes(FLAG_ONGOING_EVENT));
 
         assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -142,10 +184,10 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_oneAutoCancel() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_AUTO_CANCEL);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -153,11 +195,11 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_allAutoCancel() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", FLAG_AUTO_CANCEL);
-        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
-        children.put("c", FLAG_AUTO_CANCEL);
-        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+        children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("d", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE));
 
         assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -165,11 +207,12 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", FLAG_AUTO_CANCEL);
-        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
-        children.put("c", FLAG_AUTO_CANCEL);
-        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+        children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("d", getNotificationAttributes(
+                FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT));
 
         assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -230,11 +273,11 @@
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -248,11 +291,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -266,11 +309,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -282,11 +325,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -301,11 +344,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -329,8 +372,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary should keep FLAG_ONGOING_EVENT if any child has it
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
     }
 
     @Test
@@ -355,7 +398,8 @@
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
         // Summary is no longer ongoing
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
     }
 
     @Test
@@ -378,8 +422,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary is now ongoing
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
     }
 
     @Test
@@ -403,8 +447,8 @@
         mGroupHelper.onNotificationPosted(sbn, true);
 
         // Summary is now ongoing
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
     }
 
     @Test
@@ -430,7 +474,8 @@
         mGroupHelper.onNotificationPosted(sbn, true);
 
         // Summary is no longer ongoing
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
     }
 
     @Test
@@ -455,7 +500,7 @@
         mGroupHelper.onNotificationRemoved(notifications.get(1));
 
         // Summary is still ongoing
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -479,7 +524,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary should no longer be autocancelable
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
     }
 
     @Test
@@ -505,8 +551,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary should now autocancelable
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
     }
 
     @Test
@@ -530,7 +576,7 @@
         mGroupHelper.onNotificationPosted(sbn, true);
 
         // Summary should be still be autocancelable
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -552,7 +598,7 @@
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
         // Summary should still be autocancelable
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -565,7 +611,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -593,7 +639,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -622,7 +668,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -646,8 +692,213 @@
         verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
-        verify(mCallback, never()).addAutoGroupSummary(
-                anyInt(), anyString(), anyString(), anyInt());
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAddSummary_sameIcon_sameColor() {
+        final String pkg = "package";
+        final Icon icon = mock(Icon.class);
+        when(icon.sameAs(icon)).thenReturn(true);
+        final int iconColor = Color.BLUE;
+        final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+
+        // Add notifications with same icon and color
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+                    icon, iconColor);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+        // Check that the summary would have the same icon and color
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(attr));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+        // After auto-grouping, add new notification with the same color
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+                String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Check that the summary was updated
+        //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAddSummary_diffIcon_diffColor() {
+        final String pkg = "package";
+        final Icon initialIcon = mock(Icon.class);
+        when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+        final int initialIconColor = Color.BLUE;
+
+        // Spy GroupHelper for getMonochromeAppIcon
+        final Icon monochromeIcon = mock(Icon.class);
+        when(monochromeIcon.sameAs(monochromeIcon)).thenReturn(true);
+        GroupHelper groupHelper = spy(mGroupHelper);
+        doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+        final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
+                initialIcon, initialIconColor);
+
+        // Add notifications with same icon and color
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+                    initialIcon, initialIconColor);
+            groupHelper.onNotificationPosted(sbn, false);
+        }
+        // Check that the summary would have the same icon and color
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(initialAttr));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+        // After auto-grouping, add new notification with a different color
+        final Icon newIcon = mock(Icon.class);
+        final int newIconColor = Color.YELLOW;
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+                String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
+                newIconColor);
+        groupHelper.onNotificationPosted(sbn, true);
+
+        // Summary should be updated to the default color and the icon to the monochrome icon
+        NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
+                COLOR_DEFAULT);
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
+        final String pkg = "package";
+        final Icon initialIcon = mock(Icon.class);
+        when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+        final int initialIconColor = Color.BLUE;
+        final NotificationAttributes initialAttr = new NotificationAttributes(
+                GroupHelper.FLAG_INVALID, initialIcon, initialIconColor);
+
+        // Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color
+        ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+                    initialIcon, initialIconColor);
+            notifications.add(sbn);
+        }
+        // And an additional notification with different icon and color
+        final int lastIdx = AUTOGROUP_AT_COUNT - 1;
+        StatusBarNotification newSbn = getSbn(pkg, lastIdx,
+                String.valueOf(lastIdx), UserHandle.SYSTEM, null, mock(Icon.class),
+                Color.YELLOW);
+        notifications.add(newSbn);
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // Remove last notification (the only one with different icon and color)
+        mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+
+        // Summary should be updated to the common icon and color
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(initialAttr));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIcon_sameIcon() {
+        final String pkg = "package";
+        final Icon icon = mock(Icon.class);
+        when(icon.sameAs(icon)).thenReturn(true);
+
+        // Create notifications with the same icon
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT));
+        }
+
+        //Check that the generated summary icon is the same as the child notifications'
+        Icon summaryIcon = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+        assertThat(summaryIcon).isEqualTo(icon);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIcon_diffIcon() {
+        final String pkg = "package";
+        // Spy GroupHelper for getMonochromeAppIcon
+        final Icon monochromeIcon = mock(Icon.class);
+        GroupHelper groupHelper = spy(mGroupHelper);
+        doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+        // Create notifications with different icons
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT));
+        }
+
+        // Check that the generated summary icon is the monochrome icon
+        Icon summaryIcon = groupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+        assertThat(summaryIcon).isEqualTo(monochromeIcon);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIconColor_sameColor() {
+        final String pkg = "package";
+        final int iconColor = Color.BLUE;
+        // Create notifications with the same icon color
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor));
+        }
+
+        // Check that the generated summary icon color is the same as the child notifications'
+        int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+                childrenAttr).iconColor;
+        assertThat(summaryIconColor).isEqualTo(iconColor);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIconColor_diffColor() {
+        final String pkg = "package";
+        // Create notifications with different icon colors
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i));
+        }
+
+        // Check that the generated summary icon color is the default color
+        int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+                childrenAttr).iconColor;
+        assertThat(summaryIconColor).isEqualTo(Notification.COLOR_DEFAULT);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testMonochromeAppIcon_adaptiveIconExists() throws Exception {
+        final String pkg = "testPackage";
+        final int monochromeIconResId = 1234;
+        AdaptiveIconDrawable adaptiveIcon = mock(AdaptiveIconDrawable.class);
+        Drawable monochromeIcon = mock(Drawable.class);
+        when(mPackageManager.getApplicationIcon(pkg)).thenReturn(adaptiveIcon);
+        when(adaptiveIcon.getMonochrome()).thenReturn(monochromeIcon);
+        when(adaptiveIcon.getSourceDrawableResId()).thenReturn(monochromeIconResId);
+        assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+                .isEqualTo(monochromeIconResId);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testMonochromeAppIcon_adaptiveIconMissing_fallback() throws Exception {
+        final String pkg = "testPackage";
+        final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+        when(mPackageManager.getApplicationIcon(pkg)).thenReturn(mock(Drawable.class));
+        assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+                .isEqualTo(fallbackIconResId);
     }
 }
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 ef0ac33..f6cf4da 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -279,6 +279,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 import com.android.server.notification.NotificationManagerService.NotificationListeners;
 import com.android.server.notification.NotificationManagerService.PostNotificationTracker;
@@ -658,7 +659,8 @@
         // NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
         //  flags here.
         mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
-                Flags.FLAG_POLITE_NOTIFICATIONS);
+                Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+
         initNMS();
     }
 
@@ -1254,6 +1256,11 @@
         verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
         assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
                 captor.getValue().getIntent().getPackage());
+
+        mService.cancelScheduledTimeoutLocked(r);
+        verify(mAlarmManager).cancel(captor.capture());
+        assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
+                captor.getValue().getIntent().getPackage());
     }
 
     @Test
@@ -2327,8 +2334,9 @@
         mService.mAutobundledSummaries.put(0, new ArrayMap<>());
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
 
-        mService.updateAutobundledSummaryFlags(
-                0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
+        mService.updateAutobundledSummaryLocked(0, "pkg",
+                new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
+                    mock(Icon.class), 0), false);
         waitForIdle();
 
         assertTrue(summary.getSbn().isOngoing());
@@ -2345,7 +2353,9 @@
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
         mService.mSummaryByGroupKey.put("pkg", summary);
 
-        mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
+        mService.updateAutobundledSummaryLocked(0, "pkg",
+                new NotificationAttributes(GroupHelper.BASE_FLAGS,
+                    mock(Icon.class), 0), false);
         waitForIdle();
 
         assertFalse(summary.getSbn().isOngoing());
@@ -3422,8 +3432,8 @@
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
         when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
 
-        NotificationRecord r = mService.createAutoGroupSummary(
-                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
+        NotificationRecord r = mService.createAutoGroupSummary(temp.getUserId(),
+                temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0);
 
         assertThat(r.isImportanceFixed()).isTrue();
     }
@@ -4111,7 +4121,8 @@
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
 
-        mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                r.getKey(), 1000, null);
 
         verify(mWorkerHandler, never()).post(
                 any(NotificationManagerService.SnoozeNotificationRunnable.class));
@@ -4129,13 +4140,118 @@
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
 
-        mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                r2.getKey(), 1000, null);
 
         verify(mWorkerHandler).post(
                 any(NotificationManagerService.SnoozeNotificationRunnable.class));
     }
 
     @Test
+    public void snoozeNotificationInt_rapidSnooze_new() {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is logged.
+        verify(mAppOpsManager).noteOpNoThrow(
+                AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null,
+                null);
+    }
+
+    @Test
+    public void snoozeNotificationInt_rapidSnooze_old() {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create old notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void snoozeNotificationInt_rapidSnooze_new_flagDisabled() {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void snoozeNotificationInt_rapidSnooze_old_flagDisabled() {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create old notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
     public void testSnoozeRunnable_tooManySnoozed_singleNotification() {
         final NotificationRecord notification = generateNotificationRecord(
                 mTestNotificationChannel, 1, null, true);
@@ -9736,8 +9852,11 @@
             throws RemoteException {
         IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
-        // Set up volume to be above 0 for the sound to actually play
+        // Set up volume to be above 0, and for AudioManager to signal playback should happen,
+        // for the sound to actually play
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+        when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class)))
+                .thenReturn(true);
 
         setUpPrefsForBubbles(PKG, mUid,
                 true /* global */,
@@ -11681,7 +11800,7 @@
 
         // style + self managed call - bypasses block
         when(mTelecomManager.isInSelfManagedCall(
-                r.getSbn().getPackageName(), r.getUser())).thenReturn(true);
+                r.getSbn().getPackageName(), r.getUser(), true)).thenReturn(true);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
                 r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
 
@@ -11764,7 +11883,7 @@
         // style + self managed call - bypasses block
         mService.clearNotifications();
         reset(mUsageStats);
-        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser()))
+        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser(), true))
                 .thenReturn(true);
 
         mService.addEnqueuedNotification(r);
@@ -11957,7 +12076,7 @@
         // add summary
         mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
                 nr1.getSbn().getPackageName(), nr1.getKey(),
-                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
+                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0));
 
         // cancel both children
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -11984,8 +12103,9 @@
         // add notifications + summary for USER_SYSTEM
         mService.addNotification(nr0);
         mService.addNotification(nr1);
-        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
-                nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+        mService.addNotification(
+                mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
+                nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
 
         // add notifications + summary for USER_ALL
         NotificationRecord nr0_all =
@@ -11995,8 +12115,10 @@
 
         mService.addNotification(nr0_all);
         mService.addNotification(nr1_all);
-        mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
-                nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+        mService.addNotification(
+                mService.createAutoGroupSummary(nr0_all.getUserId(),
+                nr0_all.getSbn().getPackageName(),
+                nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
 
         // cancel both children for USER_ALL
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 670d097..130a8ca 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -66,6 +66,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
@@ -114,11 +115,13 @@
                     NotificationManager.IMPORTANCE_UNSPECIFIED);
     private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
 
-    private static final long[] CUSTOM_VIBRATION = new long[] {
+    private static final long[] CUSTOM_NOTIFICATION_VIBRATION = new long[] {
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 };
-    private static final long[] CUSTOM_CHANNEL_VIBRATION = new long[] {300, 400, 300, 400 };
+    private static final long[] CUSTOM_CHANNEL_VIBRATION_PATTERN = new long[] {300, 400, 300, 400 };
+    private static final VibrationEffect CUSTOM_CHANNEL_VIBRATION_EFFECT =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
     private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI;
     private static final AudioAttributes CUSTOM_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
@@ -135,6 +138,7 @@
         MockitoAnnotations.initMocks(this);
 
         when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator);
+        when(mVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
         final Resources res = mContext.getResources();
         when(mMockContext.getResources()).thenReturn(res);
         when(mMockContext.getPackageManager()).thenReturn(mPm);
@@ -168,8 +172,8 @@
             if (defaultVibration) {
                 defaults |= Notification.DEFAULT_VIBRATE;
             } else {
-                builder.setVibrate(CUSTOM_VIBRATION);
-                channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION);
+                builder.setVibrate(CUSTOM_NOTIFICATION_VIBRATION);
+                channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION_PATTERN);
             }
         }
         if (lights) {
@@ -217,26 +221,39 @@
         return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
     }
 
+    private StatusBarNotification getNotification(
+            long[] channelVibrationPattern,
+            VibrationEffect channelVibrationEffect,
+            boolean insistent) {
+        if (channelVibrationPattern != null) {
+            channel.setVibrationPattern(channelVibrationPattern);
+        } else if (channelVibrationEffect != null) {
+            channel.setVibrationEffect(channelVibrationEffect);
+        }
 
-    private StatusBarNotification getInsistentNotification(boolean defaultVibration) {
         final Builder builder = new Builder(mMockContext)
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .setPriority(Notification.PRIORITY_HIGH);
-        int defaults = 0;
-        if (defaultVibration) {
-            defaults |= Notification.DEFAULT_VIBRATE;
-        } else {
-            builder.setVibrate(CUSTOM_VIBRATION);
-            channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION);
-        }
-        builder.setDefaults(defaults);
-        builder.setFlag(Notification.FLAG_INSISTENT, true);
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setVibrate(CUSTOM_NOTIFICATION_VIBRATION)
+                .setFlag(Notification.FLAG_INSISTENT, insistent);
 
         Notification n = builder.build();
         return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
     }
 
+    private StatusBarNotification getNotification(
+            VibrationEffect channelVibrationEffect, boolean insistent) {
+        return getNotification(
+                /* channelVibrationPattern= */ null, channelVibrationEffect, insistent);
+    }
+
+    private StatusBarNotification getNotification(
+            long[] channelVibrationPattern, boolean insistent) {
+        return getNotification(
+                channelVibrationPattern, /* channelVibrationEffect= */ null, insistent);
+    }
+
     private StatusBarNotification getMessagingStyleNotification() {
         return getMessagingStyleNotification(mPkg);
     }
@@ -344,7 +361,7 @@
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(VibratorHelper.createWaveformVibration(
-                CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration());
+                CUSTOM_NOTIFICATION_VIBRATION, /* insistent= */ false), record.getVibration());
     }
 
     @Test
@@ -358,30 +375,137 @@
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertNotEquals(VibratorHelper.createWaveformVibration(
-                CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration());
+                CUSTOM_NOTIFICATION_VIBRATION, /* insistent= */ false), record.getVibration());
     }
 
     @Test
-    public void testVibration_custom_upgradeUsesChannel() {
+    public void testVibration_customPattern_nonInsistent_usesCustomPattern() {
         channel.enableVibration(true);
-        // post upgrade, custom vibration.
-        StatusBarNotification sbn = getNotification(PKG_O, false /* noisy */,
-                false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */,
-                false /* lights */, false /* defaultLights */, null /* group */);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ false);
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(VibratorHelper.createWaveformVibration(
-                CUSTOM_CHANNEL_VIBRATION, /* insistent= */ false), record.getVibration());
+                CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ false), record.getVibration());
     }
 
     @Test
-    public void testVibration_insistent_createsInsistentVibrationEffect() {
+    public void testVibration_customPattern_insistent_createsInsistentEffect() {
         channel.enableVibration(true);
-        StatusBarNotification sbn = getInsistentNotification(false /* defaultBuzz */);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ true);
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(VibratorHelper.createWaveformVibration(
-                CUSTOM_CHANNEL_VIBRATION, /* insistent= */ true), record.getVibration());
+                CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ true), record.getVibration());
+    }
+
+    @Test
+    public void testVibration_customEffect_flagNotEnabled_usesDefaultEffect() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        channel.enableVibration(true);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        VibrationEffect effect = record.getVibration();
+        assertNotEquals(effect, CUSTOM_CHANNEL_VIBRATION_EFFECT);
+        assertNotNull(effect);
+    }
+
+    @Test
+    public void testVibration_customEffect_effectNotSupported_usesDefaultEffect() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        when(mVibrator.areVibrationFeaturesSupported(any())).thenReturn(false);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        VibrationEffect effect = record.getVibration();
+        assertNotEquals(effect, CUSTOM_CHANNEL_VIBRATION_EFFECT);
+        assertNotNull(effect);
+    }
+
+    @Test
+    public void testVibration_customNonRepeatingEffect_nonInsistent_usesCustomEffect() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertEquals(CUSTOM_CHANNEL_VIBRATION_EFFECT, record.getVibration());
+    }
+
+    @Test
+    public void testVibration_customNonRepeatingEffect_insistent_createsInsistentEffect() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_EFFECT, /* insistent= */ true);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        VibrationEffect repeatingEffect =
+                CUSTOM_CHANNEL_VIBRATION_EFFECT
+                        .applyRepeatingIndefinitely(true, /* loopDelayMs= */ 0);
+        assertEquals(repeatingEffect, record.getVibration());
+    }
+
+    @Test
+    public void testVibration_customRepeatingEffect_nonInsistent_createsNonRepeatingCustomEffect() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        VibrationEffect repeatingEffect =
+                CUSTOM_CHANNEL_VIBRATION_EFFECT
+                        .applyRepeatingIndefinitely(true, /* loopDelayMs= */ 0);
+        StatusBarNotification sbn = getNotification(repeatingEffect, /* insistent= */ false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertEquals(CUSTOM_CHANNEL_VIBRATION_EFFECT, record.getVibration());
+    }
+
+    @Test
+    public void testVibration_customRepeatingEffect_insistent_usesCustomEffect() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        VibrationEffect repeatingEffect =
+                CUSTOM_CHANNEL_VIBRATION_EFFECT
+                        .applyRepeatingIndefinitely(true, /* loopDelayMs= */ 0);
+        StatusBarNotification sbn = getNotification(repeatingEffect, /* insistent= */ true);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertEquals(repeatingEffect, record.getVibration());
+    }
+
+    @Test
+    public void testVibration_noCustomVibration_vibrationEnabled_usesDefaultVibration() {
+        channel.enableVibration(true);
+        StatusBarNotification sbn = getNotification(
+                /* channelVibrationPattern= */ null,
+                /* channelVibrationEffect= */ null,
+                /* insistent= */ false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertNotNull(record.getVibration());
+    }
+
+    @Test
+    public void testVibration_noCustomVibration_vibrationNotEnabled_usesNoVibration() {
+        channel.enableVibration(false);
+        StatusBarNotification sbn = getNotification(
+                /* channelVibrationPattern= */ null,
+                /* channelVibrationEffect= */ null,
+                /* insistent= */ false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertNull(record.getVibration());
+    }
+
+    @Test
+    public void testVibration_customVibration_vibrationNotEnabled_usesNoVibration() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API);
+        StatusBarNotification sbn = getNotification(
+                CUSTOM_CHANNEL_VIBRATION_PATTERN, /* insistent= */ false);
+        channel.enableVibration(false);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertNull(record.getVibration());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5ddac03..8b55778 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -391,6 +391,7 @@
         assertEquals(expected.getSound(), actual.getSound());
         assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
         assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(expected.getVibrationEffect(), actual.getVibrationEffect());
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
         assertEquals(expected.getLightColor(), actual.getLightColor());
@@ -410,6 +411,7 @@
         assertEquals(parent.getSound(), actual.getSound());
         assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
         assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(parent.getVibrationEffect(), actual.getVibrationEffect());
         assertEquals(parent.getGroup(), actual.getGroup());
         assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
         assertEquals(parent.getLightColor(), actual.getLightColor());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 5d114f4..7462201 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5202,7 +5202,7 @@
         // rules for a missing package, created a long time ago and deleted a long time ago
         config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
 
-        mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+        mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
 
         assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
                 .containsExactly("ar1", "ar2", "ar3", "ar4");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 56c3ec0..c2a5824 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -146,7 +146,7 @@
 
     private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
             boolean assistScreenshotAllowed) throws Exception {
-        doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
+        doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowed();
         doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
                 .noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
         doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index c44be7b..c29547f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -56,7 +56,6 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
 import android.view.WindowManager;
 import android.window.BackAnimationAdapter;
@@ -632,8 +631,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG)
     public void testAdjacentFocusInActivityEmbedding() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
         Task task = createTask(mDefaultDisplay);
         TaskFragment primary = createTaskFragmentWithActivity(task);
         TaskFragment secondary = createTaskFragmentWithActivity(task);
@@ -645,7 +644,7 @@
         doReturn(primary).when(windowState).getTaskFragment();
 
         startBackNavigation();
-        verify(mWm).moveFocusToAdjacentWindow(any(), anyInt());
+        verify(mWm).moveFocusToActivity(any());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 09f677e7..5f18f84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -38,13 +36,12 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
@@ -56,13 +53,8 @@
  */
 @SmallTest
 @Presubmit
-public class ClientLifecycleManagerTests {
-
-    @Rule(order = 0)
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
-    @Rule(order = 1)
-    public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+@RunWith(WindowTestRunner.class)
+public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
 
     @Mock
     private IBinder mClientBinder;
@@ -86,7 +78,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mWms = mSystemServices.getWindowManagerService();
+        mWms = mSystemServicesTestRule.getWindowManagerService();
         mLifecycleManager = spy(new ClientLifecycleManager());
         mLifecycleManager.setWindowManager(mWms);
 
@@ -216,6 +208,21 @@
     }
 
     @Test
+    public void testScheduleTransactionAndLifecycleItems_shouldDispatchImmediately()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+        spyOn(mWms.mWindowPlacerLocked);
+        doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+
+        // Use non binder client to get non-recycled ClientTransaction.
+        mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
+                mLifecycleItem, true /* shouldDispatchImmediately */);
+
+        verify(mLifecycleManager).scheduleTransaction(any());
+        assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
+    }
+
+    @Test
     public void testDispatchPendingTransactions() throws RemoteException {
         mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
 
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 95850ac..f63ff6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1003,7 +1003,13 @@
         dc.computeScreenConfiguration(config, ROTATION_0);
         dc.onRequestedOverrideConfigurationChanged(config);
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, config.orientation);
-        assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalOrientation());
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalConfigurationOrientation());
+        window.setOverrideOrientation(SCREEN_ORIENTATION_NOSENSOR);
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE,
+                window.getRequestedConfigurationOrientation());
+        // Note that getNaturalOrientation is based on logical display size. So it is portrait if
+        // the display width equals to height.
+        assertEquals(Configuration.ORIENTATION_PORTRAIT, dc.getNaturalOrientation());
     }
 
     @Test
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 0c1a9c3..f42cdb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -845,6 +845,30 @@
     }
 
     @Test
+    public void testLetterboxDisplayedForWindowBelow() {
+        setUpDisplaySizeWithApp(1000, 2500);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+        // Prepare two windows, one base app window below the splash screen
+        final WindowState appWindow = addWindowToActivity(mActivity);
+        final WindowState startWindow = addWindowToActivity(mActivity, TYPE_APPLICATION_STARTING);
+        spyOn(appWindow);
+        // Base app window is letterboxed for display cutout and splash screen is fullscreen
+        doReturn(true).when(appWindow).isLetterboxedForDisplayCutout();
+
+        mActivity.mRootWindowContainer.performSurfacePlacement();
+
+        assertEquals(2, mActivity.mChildren.size());
+        // Splash screen is still the activity's main window
+        assertEquals(startWindow, mActivity.findMainWindow());
+        assertFalse(startWindow.isLetterboxedForDisplayCutout());
+
+        final Rect letterboxInnerBounds = new Rect();
+        mActivity.getLetterboxInnerBounds(letterboxInnerBounds);
+        // Letterboxed is still displayed for app window below splash screen
+        assertFalse(letterboxInnerBounds.isEmpty());
+    }
+
+    @Test
     public void testLetterboxFullscreenBoundsAndNotImeAttachable() {
         final int displayWidth = 2500;
         setUpDisplaySizeWithApp(displayWidth, 1000);
@@ -4773,8 +4797,12 @@
     }
 
     private WindowState addWindowToActivity(ActivityRecord activity) {
+        return addWindowToActivity(activity, WindowManager.LayoutParams.TYPE_BASE_APPLICATION);
+    }
+
+    private WindowState addWindowToActivity(ActivityRecord activity, int type) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-        params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+        params.type = type;
         params.setFitInsetsSides(0);
         params.setFitInsetsTypes(0);
         final TestWindowState w = new TestWindowState(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a88285a..897a3da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -527,29 +527,11 @@
 
     @Test
     public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
-        final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
-        mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
-                DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
-        mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
-        final Task task = createTask(mDisplayContent);
-        task.addChild(mTaskFragment, POSITION_TOP);
-        final ActivityRecord activity = createActivityRecord(task);
-        // Flush EVENT_APPEARED.
-        mController.dispatchPendingEvents();
-
-        // Make sure the activity is embedded in untrusted mode.
-        activity.info.applicationInfo.uid = uid + 1;
-        doReturn(pid + 1).when(activity).getPid();
-        task.effectiveUid = uid;
-        doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
-        doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
-        doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+        final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+        doReturn(false).when(activity).isUntrustedEmbeddingStateSharingAllowed();
 
         // Notify organizer if it was embedded before entered Pip.
         // Create a temporary token since the activity doesn't belong to the same process.
-        clearInvocations(mOrganizer);
-        activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
         mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
@@ -558,6 +540,30 @@
     }
 
     @Test
+    public void testOnActivityReparentedToTask_untrustedEmbed_reportedWhenAppOptIn() {
+        final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+        doReturn(true).when(activity).isUntrustedEmbeddingStateSharingAllowed();
+
+        // Notify organizer if it was embedded before entered Pip.
+        // Create a temporary token since the activity doesn't belong to the same process.
+        mController.onActivityReparentedToTask(activity);
+        mController.dispatchPendingEvents();
+
+        // Allow organizer to reparent activity in other process using the temporary token.
+        verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
+        final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
+        final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+        assertFalse(changes.isEmpty());
+        final TaskFragmentTransaction.Change change = changes.get(0);
+        assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
+        assertEquals(activity.getTask().mTaskId, change.getTaskId());
+        assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
+        assertNotEquals(activity.token, change.getActivityToken());
+        mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
+        assertApplyTransactionAllowed(mTransaction);
+    }
+
+    @Test
     public void testOnActivityReparentedToTask_trimReportedIntent() {
         // Make sure the activity pid/uid is the same as the organizer caller.
         final int pid = Binder.getCallingPid();
@@ -1868,6 +1874,34 @@
                 OP_TYPE_REORDER_TO_TOP_OF_TASK);
     }
 
+    @NonNull
+    private ActivityRecord setupUntrustedEmbeddingPipReparent() {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+                DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+        mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+        final Task task = createTask(mDisplayContent);
+        task.addChild(mTaskFragment, POSITION_TOP);
+        final ActivityRecord activity = createActivityRecord(task);
+
+        // Flush EVENT_APPEARED.
+        mController.dispatchPendingEvents();
+
+        // Make sure the activity is embedded in untrusted mode.
+        activity.info.applicationInfo.uid = uid + 1;
+        doReturn(pid + 1).when(activity).getPid();
+        task.effectiveUid = uid;
+        doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+        doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+        doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+        clearInvocations(mOrganizer);
+        activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+        return activity;
+    }
+
     private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
             @TaskFragmentOperation.OperationType int opType) {
         final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 98ca094..01bd96b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -867,24 +867,36 @@
         assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
 
         // Send request from a non-focused window with valid direction.
-        assertFalse(mWm.moveFocusToAdjacentWindow(null, winLeftBottom.mClient, View.FOCUS_RIGHT));
+        assertFalse(mWm.moveFocusToAdjacentWindow(winLeftBottom, View.FOCUS_RIGHT));
         // The focus should remain the same.
         assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
 
         // Send request from the focused window with valid direction.
-        assertTrue(mWm.moveFocusToAdjacentWindow(null, winLeftTop.mClient, View.FOCUS_RIGHT));
+        assertTrue(mWm.moveFocusToAdjacentWindow(winLeftTop, View.FOCUS_RIGHT));
         // The focus should change.
         assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
 
         // Send request from the focused window with invalid direction.
-        assertFalse(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_UP));
+        assertFalse(mWm.moveFocusToAdjacentWindow(winRightTop, View.FOCUS_UP));
         // The focus should remain the same.
         assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
 
         // Send request from the focused window with valid direction.
-        assertTrue(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_BACKWARD));
+        assertTrue(mWm.moveFocusToAdjacentWindow(winRightTop, View.FOCUS_BACKWARD));
         // The focus should change.
         assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+
+        if (Flags.embeddedActivityBackNavFlag()) {
+            // Send request to move the focus to top window from the left window.
+            assertTrue(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
+            // The focus should change.
+            assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+            // Send request to move the focus to top window from the right window.
+            assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
+            // The focus should NOT change.
+            assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+        }
     }
 
     private WindowState createAppWindow(ActivityRecord app, String name) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 74aabe1..aa9c0c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -94,7 +94,6 @@
 import com.android.server.wm.TaskOrganizerController.PendingTaskEvent;
 import com.android.window.flags.Flags;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -153,13 +152,6 @@
         return createTask(mDisplayContent);
     }
 
-    @Before
-    public void setUp() {
-        // We defer callbacks since we need to adjust task surface visibility, but for these tests,
-        // just run the callbacks synchronously
-        mWm.mAtmService.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer((r) -> r.run());
-    }
-
     @Test
     public void testAppearVanish() throws RemoteException {
         final ITaskOrganizer organizer = registerMockOrganizer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a0562aa..f02dd3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -236,6 +236,26 @@
         assertTrue(window.isOnScreen());
         window.hide(false /* doAnimation */, false /* requestAnim */);
         assertFalse(window.isOnScreen());
+
+        // Verifies that a window without animation can be hidden even if its parent is animating.
+        window.show(false /* doAnimation */, false /* requestAnim */);
+        assertTrue(window.isVisibleByPolicy());
+        window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class),
+                false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM);
+        window.mAttrs.windowAnimations = 0;
+        window.hide(true /* doAnimation */, true /* requestAnim */);
+        assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+        assertFalse(window.isVisibleByPolicy());
+        assertFalse(window.isOnScreen());
+
+        // Verifies that a window with animation can be hidden after the hide animation is finished.
+        window.show(false /* doAnimation */, false /* requestAnim */);
+        window.mAttrs.windowAnimations = android.R.style.Animation_Dialog;
+        window.hide(true /* doAnimation */, true /* requestAnim */);
+        assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+        assertTrue(window.isVisibleByPolicy());
+        window.cancelAnimation();
+        assertFalse(window.isVisibleByPolicy());
     }
 
     @Test
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 be83744..28e03bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1791,7 +1791,6 @@
         TestStartingWindowOrganizer(ActivityTaskManagerService service) {
             mAtm = service;
             mWMService = mAtm.mWindowManager;
-            mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run);
             mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
         }
 
@@ -1802,8 +1801,7 @@
         @Override
         public void addStartingWindow(StartingWindowInfo info) {
             synchronized (mWMService.mGlobalLock) {
-                final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
-                        info.appToken);
+                final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
                 IWindow iWindow = mock(IWindow.class);
                 doReturn(mock(IBinder.class)).when(iWindow).asBinder();
                 final WindowState window = WindowTestsBase.createWindow(null,
@@ -1825,8 +1823,7 @@
                 final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
                 if (appToken != null) {
                     mTaskAppMap.remove(removalInfo.taskId);
-                    final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
-                            appToken);
+                    final ActivityRecord activity = ActivityRecord.forTokenLocked(appToken);
                     WindowState win = mAppWindowMap.remove(appToken);
                     activity.removeChild(win);
                     activity.mStartingWindow = null;
diff --git a/services/usage/Android.bp b/services/usage/Android.bp
index 867773d..2c1095a 100644
--- a/services/usage/Android.bp
+++ b/services/usage/Android.bp
@@ -18,5 +18,8 @@
     name: "services.usage",
     defaults: ["platform_service_defaults"],
     srcs: [":services.usage-sources"],
-    libs: ["services.core"],
+    libs: [
+        "services.core",
+        "service-art.stubs.system_server",
+    ],
 }
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 2445f51..44f4068 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -19,7 +19,9 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import static com.android.internal.util.ArrayUtils.defeatNullable;
+import static com.android.server.pm.DexOptHelper.getArtManagerLocal;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
 import static com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
 
 import android.Manifest;
@@ -76,6 +78,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtManagedFileStats;
+import com.android.server.pm.PackageManagerLocal.FilteredSnapshot;
 import com.android.server.IoThread;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -449,7 +454,7 @@
                     }
                     if (Flags.getAppBytesByDataTypeApi()) {
                         computeAppStatsByDataTypes(
-                            stats, appInfo.sourceDir);
+                            stats, appInfo.sourceDir, packageNames[i]);
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -592,6 +597,9 @@
         res.codeBytes = stats.codeSize + stats.externalCodeSize;
         res.dataBytes = stats.dataSize + stats.externalDataSize;
         res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+        res.dexoptBytes = stats.dexoptSize;
+        res.curProfBytes = stats.curProfSize;
+        res.refProfBytes = stats.refProfSize;
         res.apkBytes = stats.apkSize;
         res.libBytes = stats.libSize;
         res.dmBytes = stats.dmSize;
@@ -946,7 +954,7 @@
     }
 
     private void computeAppStatsByDataTypes(
-        PackageStats stats, String sourceDirName) {
+        PackageStats stats, String sourceDirName, String packageName) {
 
         // Get apk, lib, dm file sizes.
         File srcDir = new File(sourceDirName);
@@ -958,5 +966,24 @@
         stats.apkSize += getFileBytesInDir(srcDir, ".apk");
         stats.dmSize += getFileBytesInDir(srcDir, ".dm");
         stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+
+        // Get dexopt, current profle and reference profile sizes.
+        if (SystemProperties.getBoolean("dalvik.vm.features.art_managed_file_stats", false)) {
+            ArtManagedFileStats artManagedFileStats;
+            try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
+                artManagedFileStats =
+                    getArtManagerLocal().getArtManagedFileStats(snapshot, packageName);
+            }
+
+            stats.dexoptSize +=
+                artManagedFileStats
+                    .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT);
+            stats.refProfSize +=
+                artManagedFileStats
+                    .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE);
+            stats.curProfSize +=
+                artManagedFileStats
+                    .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE);
+        }
     }
 }
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index b57b7c7..bb2ac51 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -6,4 +6,5 @@
 rgreenwalt@google.com
 grantmenke@google.com
 pmadapurmath@google.com
-tjstuart@google.com
\ No newline at end of file
+tjstuart@google.com
+huiwang@google.com
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index a2105b0..1df6cf7 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,6 +21,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -56,6 +57,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.flags.Flags;
 
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -1015,9 +1017,11 @@
     /**
      * Connection event used to communicate a {@link android.telephony.CallQuality} report from
      * telephony to Telecom for relaying to
-     * {@link DiagnosticCall#onCallQualityReceived(CallQuality)}.
+     * {@link CallDiagnostics#onCallQualityReceived(CallQuality)}.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public static final String EVENT_CALL_QUALITY_REPORT =
             "android.telecom.event.CALL_QUALITY_REPORT";
 
@@ -1026,6 +1030,8 @@
      * {@link android.telephony.CallQuality} data.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public static final String EXTRA_CALL_QUALITY_REPORT =
             "android.telecom.extra.CALL_QUALITY_REPORT";
 
@@ -4019,9 +4025,12 @@
     }
 
     /**
+     * Retrieves the direction of this connection.
      * @return The direction of the call.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public final @Call.Details.CallDirection int getCallDirection() {
         return mCallDirection;
     }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 536e458..01448c3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -43,6 +44,7 @@
 import com.android.internal.telecom.IConnectionService;
 import com.android.internal.telecom.IConnectionServiceAdapter;
 import com.android.internal.telecom.RemoteServiceCallback;
+import com.android.server.telecom.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -3235,27 +3237,27 @@
     }
 
     /**
-     * Called after the {@link Connection} returned by
+     * Called by Telecom after the {@link Connection} returned by
      * {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}
      * or {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} has been
      * added to the {@link ConnectionService} and sent to Telecom.
      *
-     * @param connection the {@link Connection}.
-     * @hide
+     * @param connection the {@link Connection} which was added to Telecom.
      */
-    public void onCreateConnectionComplete(Connection connection) {
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    public void onCreateConnectionComplete(@NonNull Connection connection) {
     }
 
     /**
-     * Called after the {@link Conference} returned by
+     * Called by Telecom after the {@link Conference} returned by
      * {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)}
      * or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been
      * added to the {@link ConnectionService} and sent to Telecom.
      *
-     * @param conference the {@link Conference}.
-     * @hide
+     * @param conference the {@link Conference} which was added to Telecom.
      */
-    public void onCreateConferenceComplete(Conference conference) {
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    public void onCreateConferenceComplete(@NonNull Conference conference) {
     }
 
 
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b7706a9..6bdc43e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -810,6 +810,9 @@
          * If this setter method is never called or cleared using
          * {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
          * {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+         * If this setter is called and set as an empty Set, then this {@link PhoneAccount} does
+         * not support simultaneous calling with any other {@link PhoneAccount}s registered by the
+         * same application.
          * <p>
          * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
          * were registered by the same application. Simultaneous calling across applications is
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2c6e1e4..e62bd90 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1359,8 +1359,9 @@
 
     /**
      * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
-     * calls. The returned list includes those accounts which have been explicitly enabled by
-     * the user or enabled by other users but visible to the user.
+     * calls. The returned list includes those accounts which have been explicitly enabled.
+     * In contrast to {@link #getCallCapablePhoneAccounts}, this also includes accounts from
+     * the calling user's {@link android.os.UserManager#getUserProfiles} profile group.
      *
      * @see #EXTRA_PHONE_ACCOUNT_HANDLE
      * @return A list of {@code PhoneAccountHandle} objects.
@@ -2752,17 +2753,23 @@
      *
      * @param packageName the package name of the app to check calls for.
      * @param userHandle the user handle on which to check for calls.
+     * @param hasCrossUserAccess indicates if calls should be detected across all users.
      * @return {@code true} if there are ongoing calls, {@code false} otherwise.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            Manifest.permission.INTERACT_ACROSS_USERS
+    })
     public boolean isInSelfManagedCall(@NonNull String packageName,
-            @NonNull UserHandle userHandle) {
+            @NonNull UserHandle userHandle, boolean hasCrossUserAccess) {
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
                 return service.isInSelfManagedCall(packageName, userHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), hasCrossUserAccess);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
                 e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index f1bfd22..412e827 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -395,7 +395,7 @@
      * @see TelecomServiceImpl#isInSelfManagedCall
      */
     boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
-        String callingPackage);
+        String callingPackage, boolean hasCrossUserAccess);
 
     /**
      * @see TelecomServiceImpl#addCall
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 287aa65..7607c64 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -15,4 +15,4 @@
 per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
 
 #Domain Selection is jointly owned, add additional owners for domain selection specific files
-per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com
+per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e5a94c3..f7793f3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7517,8 +7517,8 @@
          *
          * The default value for this key is
          * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String
                 KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
                         + "emergency_over_ims_supported_3gpp_network_types_int_array";
@@ -7535,8 +7535,8 @@
          *
          * The default value for this key is
          * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String
                 KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
                         + "emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
@@ -7555,8 +7555,8 @@
          * The default value for this key is
          * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
          * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY =
                 KEY_PREFIX + "emergency_over_cs_supported_access_network_types_int_array";
 
@@ -7574,8 +7574,8 @@
          * The default value for this key is
          * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
          * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String
                 KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
                         + "emergency_over_cs_roaming_supported_access_network_types_int_array";
@@ -7590,20 +7590,20 @@
 
         /**
          * Circuit switched domain.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int DOMAIN_CS = 1;
 
         /**
          * Packet switched domain over 3GPP networks.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int DOMAIN_PS_3GPP = 2;
 
         /**
          * Packet switched domain over non-3GPP networks such as Wi-Fi.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int DOMAIN_PS_NON_3GPP = 3;
 
         /**
@@ -7620,8 +7620,8 @@
          * {{@link #DOMAIN_PS_3GPP},
          * {@link #DOMAIN_CS},
          * {@link #DOMAIN_PS_NON_3GPP}}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY =
                 KEY_PREFIX + "emergency_domain_preference_int_array";
 
@@ -7639,18 +7639,22 @@
          * {{@link #DOMAIN_PS_3GPP},
          * {@link #DOMAIN_CS},
          * {@link #DOMAIN_PS_NON_3GPP}}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY =
                 KEY_PREFIX + "emergency_domain_preference_roaming_int_array";
 
         /**
-         * Specifies if emergency call shall be attempted on IMS, if PS is attached even though IMS
-         * is not registered and normal calls fallback to the CS networks.
+         * Specifies whether the emergency call shall be preferred over IMS or not
+         * irrespective of IMS registration status.
+         * If the value of the config is {@code true} then emergency calls shall prefer IMS
+         * when device is combined-attached in LTE network and IMS is not registered.
+         * If the value of the config is {@code false} then emergency calls use CS domain
+         * in the same scenario.
          *
          * The default value for this key is {@code false}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
                 KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
 
@@ -7667,32 +7671,39 @@
          * If {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true},
          * VoWi-Fi emergency call shall be attempted if Wi-Fi network is connected.
          * Otherwise, it shall be attempted if IMS is registered over Wi-Fi.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int VOWIFI_REQUIRES_NONE = 0;
 
         /**
          * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
          * and Wi-Fi calling setting is enabled. This value is applicable if the value of
          * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1;
 
         /**
          * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
-         * and Wi-Fi calling is activated successfully. This value is applicable if the value of
+         * and Wi-Fi calling is activated successfully. The device shall have the valid
+         * Entitlement ID if the user activates VoWi-Fi emergency calling successfully.
+         * This value is applicable if the value of
          * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int VOWIFI_REQUIRES_VALID_EID = 2;
 
         /**
          * Specifies the condition when emergency call shall be attempted on IMS over Wi-Fi.
          *
-         * The default value for this key is {@code #VOWIFI_REQUIRES_NONE}.
-         * @hide
+         * <p>Possible values are,
+         * {@link #VOWIFI_REQUIRES_NONE}
+         * {@link #VOWIFI_REQUIRES_SETTING_ENABLED}
+         * {@link #VOWIFI_REQUIRES_VALID_EID}
+         *
+         * The default value for this key is {@link #VOWIFI_REQUIRES_NONE}.
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT =
                 KEY_PREFIX + "emergency_vowifi_requires_condition_int";
 
@@ -7703,8 +7714,8 @@
          * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY}.
          *
          * The default value for this key is 1.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT =
                 KEY_PREFIX + "maximum_number_of_emergency_tries_over_vowifi_int";
 
@@ -7712,14 +7723,14 @@
          * Emergency scan timer to wait for scan results from radio before attempting the call
          * over Wi-Fi. On timer expiry, if emergency call on Wi-Fi is allowed and possible,
          * telephony shall cancel the scan and place the call on Wi-Fi. If emergency call on Wi-Fi
-         * is not possible, then domain seleciton continues to wait for the scan result from the
+         * is not possible, then domain selection continues to wait for the scan result from the
          * radio. If an emergency scan result is received before the timer expires, the timer shall
          * be stopped and no dialing over Wi-Fi will be tried. If this value is set to 0, then
          * the timer is never started and domain selection waits for the scan result from the radio.
          *
          * The default value for the timer is 10 seconds.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT =
                 KEY_PREFIX + "emergency_scan_timer_sec_int";
 
@@ -7737,8 +7748,8 @@
          * started.
          *
          * The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT =
                 KEY_PREFIX + "maximum_cellular_search_timer_sec_int";
 
@@ -7753,21 +7764,21 @@
         /**
          * No specific preference given to the modem. Modem can return an emergency
          * capable network either with limited service or full service.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int SCAN_TYPE_NO_PREFERENCE = 0;
 
         /**
          * Modem will attempt to camp on a network with full service only.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int SCAN_TYPE_FULL_SERVICE = 1;
 
         /**
          * Telephony shall attempt full service scan first.
          * If a full service network is not found, telephony shall attempt a limited service scan.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2;
 
         /**
@@ -7779,8 +7790,8 @@
          * {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}
          *
          * The default value for this key is {@link #SCAN_TYPE_NO_PREFERENCE}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT =
                 KEY_PREFIX + "emergency_network_scan_type_int";
 
@@ -7791,8 +7802,8 @@
          * If this value is set to 0, the timer shall be disabled.
          *
          * The default value for this key is 0.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT =
                 KEY_PREFIX + "emergency_call_setup_timer_on_current_network_sec_int";
 
@@ -7801,8 +7812,8 @@
          * This is applicable only for the case PS is in service.
          *
          * The default value for this key is {@code false}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL =
                 KEY_PREFIX + "emergency_requires_ims_registration_bool";
 
@@ -7811,8 +7822,8 @@
          * over NR. If not, CS will be preferred.
          *
          * The default value for this key is {@code false}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL =
                 KEY_PREFIX + "emergency_lte_preferred_after_nr_failed_bool";
 
@@ -7820,8 +7831,8 @@
          * Specifies the numbers to be dialed over CDMA network in case of dialing over CS network.
          *
          * The default value for this key is an empty string array.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY =
                 KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
 
@@ -7830,8 +7841,8 @@
          * only when VoLTE is enabled.
          *
          * The default value for this key is {@code false}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
                 KEY_PREFIX + "emergency_requires_volte_enabled_bool";
 
@@ -7842,8 +7853,8 @@
          * @see #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT
          * @see #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT
          * @see #KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final int REDIAL_TIMER_DISABLED = 0;
 
         /**
@@ -7855,8 +7866,8 @@
          * This value should be greater than the value of {@link #KEY_EMERGENCY_SCAN_TIMER_SEC_INT}.
          *
          * The default value for the timer is 120 seconds.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT =
                 KEY_PREFIX + "cross_stack_redial_timer_sec_int";
 
@@ -7871,8 +7882,8 @@
          * in the roaming networks and non-domestic networks.
          *
          * The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT =
                 KEY_PREFIX + "quick_cross_stack_redial_timer_sec_int";
 
@@ -7881,11 +7892,24 @@
          * the device is registered to the network.
          *
          * The default value is {@code true}.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL =
                 KEY_PREFIX + "start_quick_cross_stack_redial_timer_when_registered_bool";
 
+        /**
+         * Indicates whether limited service only scanning will be requested after VoLTE fails.
+         * This value is applicable if the value of
+         * {@link #KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT} is any of {@link #SCAN_TYPE_NO_PREFERENCE}
+         * or {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}.
+         *
+         * The default value is {@code false}.
+         */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
+        public static final String
+                KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL =
+                    KEY_PREFIX + "scan_limited_service_after_volte_failure_bool";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7957,6 +7981,7 @@
             defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
             defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
                     true);
+            defaults.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL, false);
 
             return defaults;
         }
@@ -9692,6 +9717,19 @@
             "remove_satellite_plmn_in_manual_network_scan_bool";
 
     /**
+     * Determine whether to override roaming Wi-Fi Calling preference when device is connected to
+     * non-terrestrial network.
+     * {@code true}  - roaming preference cannot be changed by user independently.
+     *                 Roaming preference is overridden to
+     *                 {@link com.android.ims.ImsConfig.WfcModeFeatureValueConstants#WIFI_PREFERRED}
+     * {@code false} - roaming preference can be changed by user independently and is not
+     *                 overridden when device is connected to non-terrestrial network.
+     * @hide
+     */
+    public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL =
+            "override_wfc_roaming_mode_while_using_ntn_bool";
+
+    /**
      * An integer key holds the time interval for refreshing or re-querying the satellite
      * entitlement status from the entitlement server to ensure it is the latest.
      *
@@ -10817,6 +10855,7 @@
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
                 CellSignalStrengthLte.USE_RSRP);
         sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
+        sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true);
         sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
         sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 2b0d626..d5db612 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -174,6 +174,7 @@
     private int mMultiSimPolicy;
     @CarrierRestrictionStatus
     private int mCarrierRestrictionStatus;
+    private boolean mUseCarrierLockInfo;
 
     private CarrierRestrictionRules() {
         mAllowedCarriers = new ArrayList<CarrierIdentifier>();
@@ -183,6 +184,7 @@
         mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
         mMultiSimPolicy = MULTISIM_POLICY_NONE;
         mCarrierRestrictionStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN;
+        mUseCarrierLockInfo = false;
     }
 
     private CarrierRestrictionRules(Parcel in) {
@@ -198,6 +200,7 @@
         if (Flags.carrierRestrictionRulesEnhancement()) {
             in.readTypedList(mAllowedCarrierInfo, CarrierInfo.CREATOR);
             in.readTypedList(mExcludedCarrierInfo, CarrierInfo.CREATOR);
+            mUseCarrierLockInfo = in.readBoolean();
         }
     }
 
@@ -213,6 +216,14 @@
      * Indicates if all carriers are allowed
      */
     public boolean isAllCarriersAllowed() {
+        if (Flags.carrierRestrictionStatus() && mCarrierRestrictionStatus
+                == TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED) {
+            return true;
+        }
+        if (Flags.carrierRestrictionRulesEnhancement() && mUseCarrierLockInfo) {
+            return (mAllowedCarrierInfo.isEmpty() && mExcludedCarrierInfo.isEmpty()
+                    && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED);
+        }
         return (mAllowedCarriers.isEmpty() && mExcludedCarriers.isEmpty()
                 && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED);
     }
@@ -419,6 +430,7 @@
         if (Flags.carrierRestrictionRulesEnhancement()) {
             out.writeTypedList(mAllowedCarrierInfo);
             out.writeTypedList(mExcludedCarrierInfo);
+            out.writeBoolean(mUseCarrierLockInfo);
         }
     }
 
@@ -451,7 +463,8 @@
     public String toString() {
         return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
                 + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
-                + ", multisim policy:" + mMultiSimPolicy + getCarrierInfoList() + ")";
+                + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() +
+                "  mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + ")";
     }
 
     private String getCarrierInfoList() {
@@ -490,6 +503,7 @@
                         TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED;
                 mRules.mAllowedCarrierInfo.clear();
                 mRules.mExcludedCarrierInfo.clear();
+                mRules.mUseCarrierLockInfo = false;
             }
             return this;
         }
@@ -572,5 +586,16 @@
             mRules.mExcludedCarrierInfo = new ArrayList<CarrierInfo>(excludedCarrierInfo);
             return this;
         }
+
+        /**
+         * set whether the HAL radio supports the advanced carrier lock features or not.
+         *
+         * @param carrierLockInfoSupported advanced carrierInfo changes supported or not
+         * @hide
+         */
+        public @NonNull Builder setCarrierLockInfoFeature(boolean carrierLockInfoSupported) {
+            mRules.mUseCarrierLockInfo = carrierLockInfoSupported;
+            return this;
+        }
     }
 }
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index abcce5f..0f54e8d 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -16,12 +16,14 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.IBinder;
@@ -40,6 +42,7 @@
 import com.android.internal.telephony.ITransportSelectorResultCallback;
 import com.android.internal.telephony.IWwanSelectorCallback;
 import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
@@ -55,12 +58,38 @@
 import java.util.function.Consumer;
 
 /**
- * Main domain selection implementation for various telephony features.
- *
- * The telephony framework will bind to the {@link DomainSelectionService}.
+ * Base domain selection implementation.
+ * <p>
+ * Services that extend {@link DomainSelectionService} must register the service in their
+ * AndroidManifest.xml to be detected by the framework.
+ * <p>
+ * 1) The application must declare that they use the
+ * android.permission.BIND_DOMAIN_SELECTION_SERVICE permission.
+ * <p>
+ * 2) The DomainSelectionService definition in the manifest must follow this format:
+ * <pre>
+ * {@code
+ * ...
+ * <service android:name=".EgDomainSelectionService"
+ *    android:permission="android.permission.BIND_DOMAIN_SELECTION_SERVICE" >
+ *    <intent-filter>
+ *       <action android:name="android.telephony.DomainSelectionService" />
+ *    </intent-filter>
+ * </service>
+ * ...
+ * }
+ * </pre>
+ * <p>
+ * The ComponentName corresponding to this DomainSelectionService component MUST also be set
+ * as the system domain selection implementation in order to be bound.
+ * The system domain selection implementation is set in the device overlay for
+ * {@code config_domain_selection_service_component_name}
+ * in {@code packages/services/Telephony/res/values/config.xml}.
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
 public class DomainSelectionService extends Service {
 
     private static final String LOG_TAG = "DomainSelectionService";
@@ -78,16 +107,13 @@
     @IntDef(prefix = "SELECTOR_TYPE_",
             value = {
                     SELECTOR_TYPE_CALLING,
-                    SELECTOR_TYPE_SMS,
-                    SELECTOR_TYPE_UT})
+                    SELECTOR_TYPE_SMS})
     public @interface SelectorType {}
 
     /** Indicates the domain selector type for calling. */
     public static final int SELECTOR_TYPE_CALLING = 1;
     /** Indicates the domain selector type for sms. */
     public static final int SELECTOR_TYPE_SMS = 2;
-    /** Indicates the domain selector type for supplementary services. */
-    public static final int SELECTOR_TYPE_UT = 3;
 
     /** Indicates that the modem can scan for emergency service as per modem’s implementation. */
     public static final int SCAN_TYPE_NO_PREFERENCE = 0;
@@ -110,51 +136,52 @@
     /**
      * Contains attributes required to determine the domain for a telephony service.
      */
+    @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
     public static final class SelectionAttributes implements Parcelable {
 
         private static final String TAG = "SelectionAttributes";
 
-        private int mSlotId;
+        private int mSlotIndex;
         private int mSubId;
         private @Nullable String mCallId;
-        private @Nullable String mNumber;
+        private @Nullable Uri mAddress;
         private @SelectorType int mSelectorType;
         private boolean mIsVideoCall;
         private boolean mIsEmergency;
+        private boolean mIsTestEmergencyNumber;
         private boolean mIsExitedFromAirplaneMode;
-        //private @Nullable UtAttributes mUtAttributes;
         private @Nullable ImsReasonInfo mImsReasonInfo;
         private @PreciseDisconnectCauses int mCause;
         private @Nullable EmergencyRegResult mEmergencyRegResult;
 
         /**
-         * @param slotId The slot identifier.
-         * @param subId The subscription identifier.
+         * @param slotIndex The logical slot index.
+         * @param subscriptionId The subscription identifier.
          * @param callId The call identifier.
-         * @param number The dialed number.
+         * @param address The dialed address.
          * @param selectorType Indicates the requested domain selector type.
          * @param video Indicates it's a video call.
          * @param emergency Indicates it's emergency service.
+         * @param isTest Indicates it's a test emergency number.
          * @param exited {@code true} if the request caused the device to move out of airplane mode.
          * @param imsReasonInfo The reason why the last PS attempt failed.
          * @param cause The reason why the last CS attempt failed.
          * @param regResult The current registration result for emergency services.
          */
-        private SelectionAttributes(int slotId, int subId, @Nullable String callId,
-                @Nullable String number, @SelectorType int selectorType,
-                boolean video, boolean emergency, boolean exited,
-                /*UtAttributes attr,*/
+        private SelectionAttributes(int slotIndex, int subscriptionId, @Nullable String callId,
+                @Nullable Uri address, @SelectorType int selectorType,
+                boolean video, boolean emergency, boolean isTest, boolean exited,
                 @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
                 @Nullable EmergencyRegResult regResult) {
-            mSlotId = slotId;
-            mSubId = subId;
+            mSlotIndex = slotIndex;
+            mSubId = subscriptionId;
             mCallId = callId;
-            mNumber = number;
+            mAddress = address;
             mSelectorType = selectorType;
             mIsVideoCall = video;
             mIsEmergency = emergency;
+            mIsTestEmergencyNumber = isTest;
             mIsExitedFromAirplaneMode = exited;
-            //mUtAttributes = attr;
             mImsReasonInfo = imsReasonInfo;
             mCause = cause;
             mEmergencyRegResult = regResult;
@@ -167,14 +194,14 @@
          * @hide
          */
         public SelectionAttributes(@NonNull SelectionAttributes s) {
-            mSlotId = s.mSlotId;
+            mSlotIndex = s.mSlotIndex;
             mSubId = s.mSubId;
             mCallId = s.mCallId;
-            mNumber = s.mNumber;
+            mAddress = s.mAddress;
             mSelectorType = s.mSelectorType;
             mIsEmergency = s.mIsEmergency;
+            mIsTestEmergencyNumber = s.mIsTestEmergencyNumber;
             mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
-            //mUtAttributes = s.mUtAttributes;
             mImsReasonInfo = s.mImsReasonInfo;
             mCause = s.mCause;
             mEmergencyRegResult = s.mEmergencyRegResult;
@@ -188,16 +215,16 @@
         }
 
         /**
-         * @return The slot identifier.
+         * @return The logical slot index.
          */
-        public int getSlotId() {
-            return mSlotId;
+        public int getSlotIndex() {
+            return mSlotIndex;
         }
 
         /**
          * @return The subscription identifier.
          */
-        public int getSubId() {
+        public int getSubscriptionId() {
             return mSubId;
         }
 
@@ -209,10 +236,10 @@
         }
 
         /**
-         * @return The dialed number.
+         * @return The dialed address.
          */
-        public @Nullable String getNumber() {
-            return mNumber;
+        public @Nullable Uri getAddress() {
+            return mAddress;
         }
 
         /**
@@ -237,18 +264,19 @@
         }
 
         /**
+         * @return {@code true} if the dialed number is a test emergency number.
+         */
+        public boolean isTestEmergencyNumber() {
+            return mIsTestEmergencyNumber;
+        }
+
+        /**
          * @return {@code true} if the request caused the device to move out of airplane mode.
          */
         public boolean isExitedFromAirplaneMode() {
             return mIsExitedFromAirplaneMode;
         }
 
-        /*
-        public @Nullable UtAttributes getUtAttributes();
-            return mUtAttributes;
-        }
-        */
-
         /**
          * @return The PS disconnect cause if trying over PS resulted in a failure and
          *         reselection is required.
@@ -274,13 +302,14 @@
 
         @Override
         public @NonNull String toString() {
-            return "{ slotId=" + mSlotId
+            return "{ slotIndex=" + mSlotIndex
                     + ", subId=" + mSubId
                     + ", callId=" + mCallId
-                    + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+                    + ", address=" + (Build.IS_DEBUGGABLE ? mAddress : "***")
                     + ", type=" + mSelectorType
                     + ", videoCall=" + mIsVideoCall
                     + ", emergency=" + mIsEmergency
+                    + ", isTest=" + mIsTestEmergencyNumber
                     + ", airplaneMode=" + mIsExitedFromAirplaneMode
                     + ", reasonInfo=" + mImsReasonInfo
                     + ", cause=" + mCause
@@ -293,13 +322,13 @@
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             SelectionAttributes that = (SelectionAttributes) o;
-            return mSlotId == that.mSlotId && mSubId == that.mSubId
+            return mSlotIndex == that.mSlotIndex && mSubId == that.mSubId
                     && TextUtils.equals(mCallId, that.mCallId)
-                    && TextUtils.equals(mNumber, that.mNumber)
+                    && equalsHandlesNulls(mAddress, that.mAddress)
                     && mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
                     && mIsEmergency == that.mIsEmergency
+                    && mIsTestEmergencyNumber == that.mIsTestEmergencyNumber
                     && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
-                    //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
                     && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
                     && mCause == that.mCause
                     && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
@@ -307,9 +336,9 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mCallId, mNumber, mImsReasonInfo,
-                    mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
-                    mSlotId, mSubId, mSelectorType, mCause);
+            return Objects.hash(mCallId, mAddress, mImsReasonInfo,
+                    mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode,
+                    mEmergencyRegResult, mSlotIndex, mSubId, mSelectorType, mCause);
         }
 
         @Override
@@ -319,30 +348,31 @@
 
         @Override
         public void writeToParcel(@NonNull Parcel out, int flags) {
-            out.writeInt(mSlotId);
+            out.writeInt(mSlotIndex);
             out.writeInt(mSubId);
             out.writeString8(mCallId);
-            out.writeString8(mNumber);
+            out.writeParcelable(mAddress, 0);
             out.writeInt(mSelectorType);
             out.writeBoolean(mIsVideoCall);
             out.writeBoolean(mIsEmergency);
+            out.writeBoolean(mIsTestEmergencyNumber);
             out.writeBoolean(mIsExitedFromAirplaneMode);
-            //out.writeParcelable(mUtAttributes, 0);
             out.writeParcelable(mImsReasonInfo, 0);
             out.writeInt(mCause);
             out.writeParcelable(mEmergencyRegResult, 0);
         }
 
         private void readFromParcel(@NonNull Parcel in) {
-            mSlotId = in.readInt();
+            mSlotIndex = in.readInt();
             mSubId = in.readInt();
             mCallId = in.readString8();
-            mNumber = in.readString8();
+            mAddress = in.readParcelable(Uri.class.getClassLoader(),
+                    android.net.Uri.class);
             mSelectorType = in.readInt();
             mIsVideoCall = in.readBoolean();
             mIsEmergency = in.readBoolean();
+            mIsTestEmergencyNumber = in.readBoolean();
             mIsExitedFromAirplaneMode = in.readBoolean();
-            //mUtAttributes = s.mUtAttributes;
             mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
                     android.telephony.ims.ImsReasonInfo.class);
             mCause = in.readInt();
@@ -370,16 +400,17 @@
         /**
          * Builder class creating a new instance.
          */
+        @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
         public static final class Builder {
-            private final int mSlotId;
+            private final int mSlotIndex;
             private final int mSubId;
             private @Nullable String mCallId;
-            private @Nullable String mNumber;
+            private @Nullable Uri mAddress;
             private final @SelectorType int mSelectorType;
             private boolean mIsVideoCall;
             private boolean mIsEmergency;
+            private boolean mIsTestEmergencyNumber;
             private boolean mIsExitedFromAirplaneMode;
-            //private @Nullable UtAttributes mUtAttributes;
             private @Nullable ImsReasonInfo mImsReasonInfo;
             private @PreciseDisconnectCauses int mCause;
             private @Nullable EmergencyRegResult mEmergencyRegResult;
@@ -387,9 +418,9 @@
             /**
              * Default constructor for Builder.
              */
-            public Builder(int slotId, int subId, @SelectorType int selectorType) {
-                mSlotId = slotId;
-                mSubId = subId;
+            public Builder(int slotIndex, int subscriptionId, @SelectorType int selectorType) {
+                mSlotIndex = slotIndex;
+                mSubId = subscriptionId;
                 mSelectorType = selectorType;
             }
 
@@ -405,35 +436,46 @@
             }
 
             /**
-             * Sets the dialed number.
+             * Sets the dialed address.
              *
-             * @param number The dialed number.
+             * @param address The dialed address.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setNumber(@NonNull String number) {
-                mNumber = number;
+            public @NonNull Builder setAddress(@NonNull Uri address) {
+                mAddress = address;
                 return this;
             }
 
             /**
              * Sets whether it's a video call or not.
              *
-             * @param video Indicates it's a video call.
+             * @param isVideo Indicates it's a video call.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setVideoCall(boolean video) {
-                mIsVideoCall = video;
+            public @NonNull Builder setVideoCall(boolean isVideo) {
+                mIsVideoCall = isVideo;
                 return this;
             }
 
             /**
              * Sets whether it's an emergency service or not.
              *
-             * @param emergency Indicates it's emergency service.
+             * @param isEmergency Indicates it's emergency service.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setEmergency(boolean emergency) {
-                mIsEmergency = emergency;
+            public @NonNull Builder setEmergency(boolean isEmergency) {
+                mIsEmergency = isEmergency;
+                return this;
+            }
+
+            /**
+             * Sets whether it's a test emergency number or not.
+             *
+             * @param isTest Indicates it's a test emergency number.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setTestEmergencyNumber(boolean isTest) {
+                mIsTestEmergencyNumber = isTest;
                 return this;
             }
 
@@ -450,20 +492,6 @@
             }
 
             /**
-             * Sets the Ut service attributes.
-             * Only applicable for SELECTOR_TYPE_UT
-             *
-             * @param attr Ut services attributes.
-             * @return The same instance of the builder.
-             */
-            /*
-            public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
-                mUtAttributes = attr;
-                return this;
-            }
-            */
-
-            /**
              * Sets an optional reason why the last PS attempt failed.
              *
              * @param info The reason why the last PS attempt failed.
@@ -501,9 +529,10 @@
              * @return The SelectionAttributes object.
              */
             public @NonNull SelectionAttributes build() {
-                return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
-                        mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
-                        mImsReasonInfo, mCause, mEmergencyRegResult);
+                return new SelectionAttributes(mSlotIndex, mSubId, mCallId, mAddress,
+                        mSelectorType, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber,
+                        mIsExitedFromAirplaneMode, mImsReasonInfo,
+                        mCause, mEmergencyRegResult);
             }
         }
     }
@@ -546,19 +575,6 @@
         }
 
         @Override
-        public @NonNull WwanSelectorCallback onWwanSelected() {
-            WwanSelectorCallback callback = null;
-            try {
-                IWwanSelectorCallback cb = mCallback.onWwanSelected();
-                callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
-            } catch (Exception e) {
-                Rlog.e(TAG, "onWwanSelected e=" + e);
-            }
-
-            return callback;
-        }
-
-        @Override
         public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
             try {
                 mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
@@ -627,15 +643,6 @@
             }
 
             @Override
-            public void cancelSelection() {
-                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
-                if (domainSelector == null) return;
-
-                executeMethodAsyncNoException(mExecutor,
-                        () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
-            }
-
-            @Override
             public void reselectDomain(@NonNull SelectionAttributes attr) {
                 final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
                 if (domainSelector == null) return;
@@ -688,14 +695,15 @@
 
         @Override
         public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
-                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+                @EmergencyScanType int scanType, boolean resetScan,
+                @NonNull CancellationSignal signal,
                 @NonNull Consumer<EmergencyRegResult> consumer) {
             try {
                 if (signal != null) signal.setOnCancelListener(this);
                 mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
                 mCallback.onRequestEmergencyNetworkScan(
                         preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
-                        scanType, mResultCallback);
+                        scanType, resetScan, mResultCallback);
             } catch (Exception e) {
                 Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
             }
@@ -738,7 +746,15 @@
     private @NonNull Executor mExecutor;
 
     /**
-     * Selects a domain for the given operation.
+     * Selects a calling domain given the SelectionAttributes of the call request.
+     * <p>
+     * When the framework generates a request to place a call, {@link #onDomainSelection}
+     * will be called in order to determine the domain (CS or PS). For PS calls, the transport
+     * (WWAN or WLAN) will also need to be determined.
+     * <p>
+     * Once the domain/transport has been selected or an error has occurred,
+     * {@link TransportSelectorCallback} must be used to communicate the result back
+     * to the framework.
      *
      * @param attr Required to determine the domain.
      * @param callback The callback instance being registered.
@@ -748,23 +764,24 @@
     }
 
     /**
-     * Notifies the change in {@link ServiceState} for a specific slot.
+     * Notifies the change in {@link ServiceState} for a specific logical slot index.
      *
-     * @param slotId For which the state changed.
-     * @param subId For which the state changed.
+     * @param slotIndex For which the state changed.
+     * @param subscriptionId For which the state changed.
      * @param serviceState Updated {@link ServiceState}.
      */
-    public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+    public void onServiceStateUpdated(int slotIndex, int subscriptionId,
+            @NonNull ServiceState serviceState) {
     }
 
     /**
-     * Notifies the change in {@link BarringInfo} for a specific slot.
+     * Notifies the change in {@link BarringInfo} for a specific logical slot index.
      *
-     * @param slotId For which the state changed.
-     * @param subId For which the state changed.
+     * @param slotIndex For which the state changed.
+     * @param subscriptionId For which the state changed.
      * @param info Updated {@link BarringInfo}.
      */
-    public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+    public void onBarringInfoUpdated(int slotIndex, int subscriptionId, @NonNull BarringInfo info) {
     }
 
     private final IBinder mDomainSelectionServiceController =
@@ -779,16 +796,19 @@
         }
 
         @Override
-        public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+        public void updateServiceState(int slotIndex, int subscriptionId,
+                @NonNull ServiceState serviceState) {
             executeMethodAsyncNoException(getCachedExecutor(),
-                    () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
-                            subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+                    () -> DomainSelectionService.this.onServiceStateUpdated(slotIndex,
+                            subscriptionId, serviceState), LOG_TAG, "onServiceStateUpdated");
         }
 
         @Override
-        public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+        public void updateBarringInfo(int slotIndex, int subscriptionId,
+                @NonNull BarringInfo info) {
             executeMethodAsyncNoException(getCachedExecutor(),
-                    () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+                    () -> DomainSelectionService.this.onBarringInfoUpdated(slotIndex,
+                    subscriptionId, info),
                     LOG_TAG, "onBarringInfoUpdated");
         }
     };
@@ -816,7 +836,8 @@
 
     /** @hide */
     @Override
-    public IBinder onBind(Intent intent) {
+    public @Nullable IBinder onBind(@Nullable Intent intent) {
+        if (intent == null) return null;
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             Log.i(LOG_TAG, "DomainSelectionService Bound.");
             return mDomainSelectionServiceController;
@@ -825,13 +846,13 @@
     }
 
     /**
-     * The DomainSelectionService will be able to define an {@link Executor} that the service
-     * can use to execute the methods. It has set the default executor as Runnable::run,
+     * The Executor to use when calling callback methods from the framework.
+     * <p>
+     * By default, calls from the framework will use Binder threads to call these methods.
      *
-     * @return An {@link Executor} to be used.
+     * @return an {@link Executor} used to execute methods called remotely by the framework.
      */
-    @SuppressLint("OnNameExpected")
-    public @NonNull Executor getExecutor() {
+    public @NonNull Executor onCreateExecutor() {
         return Runnable::run;
     }
 
@@ -845,7 +866,7 @@
     public @NonNull Executor getCachedExecutor() {
         synchronized (mExecutorLock) {
             if (mExecutor == null) {
-                Executor e = getExecutor();
+                Executor e = onCreateExecutor();
                 mExecutor = (e != null) ? e : Runnable::run;
             }
             return mExecutor;
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
index 0871831..5d25d3a 100644
--- a/telephony/java/android/telephony/DomainSelector.java
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -16,22 +16,23 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.telephony.DomainSelectionService.SelectionAttributes;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * Implemented as part of the {@link DomainSelectionService} to implement domain selection
- * for a specific use case.
+ * for a specific use case and receive signals from the framework to reselect a new domain
+ * when a previous domain selection fails or finish a selection when the call connects successfully.
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
 public interface DomainSelector {
     /**
-     * Cancel an ongoing selection operation. It is up to the DomainSelectionService
-     * to clean up all ongoing operations with the framework.
-     */
-    void cancelSelection();
-
-    /**
      * Reselect a domain due to the call not setting up properly.
      *
      * @param attr attributes required to select the domain.
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
index 5aed412..15579be 100644
--- a/telephony/java/android/telephony/EmergencyRegResult.java
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -16,17 +16,25 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
- * Contains attributes required to determine the domain for a telephony service
+ * Contains attributes required to determine the domain for a telephony service, including
+ * the network registration state.
+ *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
 public final class EmergencyRegResult implements Parcelable {
 
     /**
@@ -77,7 +85,7 @@
      * The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
      * empty string if unknown.
      */
-    private @NonNull String mIso;
+    private @NonNull String mCountryIso;
 
     /**
      * Constructor
@@ -108,7 +116,7 @@
         mNwProvidedEmf = emf;
         mMcc = mcc;
         mMnc = mnc;
-        mIso = iso;
+        mCountryIso = iso;
     }
 
     /**
@@ -127,7 +135,7 @@
         mNwProvidedEmf = s.mNwProvidedEmf;
         mMcc = s.mMcc;
         mMnc = s.mMnc;
-        mIso = s.mIso;
+        mCountryIso = s.mCountryIso;
     }
 
     /**
@@ -226,8 +234,8 @@
      *
      * @return Country code.
      */
-    public @NonNull String getIso() {
-        return mIso;
+    public @NonNull String getCountryIso() {
+        return mCountryIso;
     }
 
     @Override
@@ -242,7 +250,7 @@
                 + ", emf=" + mNwProvidedEmf
                 + ", mcc=" + mMcc
                 + ", mnc=" + mMnc
-                + ", iso=" + mIso
+                + ", iso=" + mCountryIso
                 + " }";
     }
 
@@ -260,7 +268,7 @@
                 && mNwProvidedEmf == that.mNwProvidedEmf
                 && TextUtils.equals(mMcc, that.mMcc)
                 && TextUtils.equals(mMnc, that.mMnc)
-                && TextUtils.equals(mIso, that.mIso);
+                && TextUtils.equals(mCountryIso, that.mCountryIso);
     }
 
     @Override
@@ -268,7 +276,7 @@
         return Objects.hash(mAccessNetworkType, mRegState, mDomain,
                 mIsVopsSupported, mIsEmcBearerSupported,
                 mNwProvidedEmc, mNwProvidedEmf,
-                mMcc, mMnc, mIso);
+                mMcc, mMnc, mCountryIso);
     }
 
     @Override
@@ -287,7 +295,7 @@
         out.writeInt(mNwProvidedEmf);
         out.writeString8(mMcc);
         out.writeString8(mMnc);
-        out.writeString8(mIso);
+        out.writeString8(mCountryIso);
     }
 
     private void readFromParcel(@NonNull Parcel in) {
@@ -300,7 +308,7 @@
         mNwProvidedEmf = in.readInt();
         mMcc = in.readString8();
         mMnc = in.readString8();
-        mIso = in.readString8();
+        mCountryIso = in.readString8();
     }
 
     public static final @NonNull Creator<EmergencyRegResult> CREATOR =
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 7ccc27e..0c324e6 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -649,12 +649,19 @@
     }
 
     /**
-     * @return Reason for denial if the registration state is {@link #REGISTRATION_STATE_DENIED}.
-     * Depending on {@code accessNetworkTechnology}, the values are defined in 3GPP TS 24.008
-     * 10.5.3.6 for UMTS, 3GPP TS 24.301 9.9.3.9 for LTE, and 3GPP2 A.S0001 6.2.2.44 for CDMA
-     * @hide
+     * Get the 3GPP/3GPP2 reason code indicating why registration failed.
+     *
+     * Returns the reason code for non-transient registration failures. Typically this method will
+     * only return the last reason code received during a network selection procedure. The reason
+     * code is system-specific; however, the reason codes for both 3GPP and 3GPP2 systems are
+     * largely equivalent across generations.
+     *
+     * @return registration reject cause if available, otherwise 0. Depending on
+     * {@link #getAccessNetworkTechnology}, the values are defined in 3GPP TS 24.008 10.5.3.6 for
+     * WCDMA/UMTS, 3GPP TS 24.301 9.9.3.9 for LTE/EPS, 3GPP 24.501 Annex A for NR/5GS, or 3GPP2
+     * A.S0001 6.2.2.44 for CDMA.
      */
-    @SystemApi
+    @FlaggedApi(Flags.FLAG_NETWORK_REGISTRATION_INFO_REJECT_CAUSE)
     public int getRejectCause() {
         return mRejectCause;
     }
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 1cfd22c..d9437ab 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -16,8 +16,11 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.SystemApi;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * Contains precise disconnect call causes generated by the framework and the RIL.
  * @hide
@@ -238,18 +241,18 @@
     /**
      * Dialing emergency calls is currently unavailable.
      * The call should be redialed on the other subscription silently.
-     * If there is no other subscription available, the call may be redialed
+     * If there are no other subscriptions available then the call may be redialed
      * on this subscription again.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
     public static final int EMERGENCY_TEMP_FAILURE                           = 325;
     /**
      * Dialing emergency calls is currently unavailable.
      * The call should be redialed on the other subscription silently.
-     * Even if there is no other subscription available, the call should not
+     * If there are no other subscriptions available then the call should not
      * be redialed on this subscription again.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
     public static final int EMERGENCY_PERM_FAILURE                           = 326;
 
     /** Mobile station (MS) is locked until next power cycle. */
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a188581..fdd0ba9 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -40,6 +40,7 @@
 import android.telephony.SubscriptionManager.ProfileClass;
 import android.telephony.SubscriptionManager.SimDisplayNameSource;
 import android.telephony.SubscriptionManager.SubscriptionType;
+import android.telephony.SubscriptionManager.TransferStatus;
 import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -236,6 +237,11 @@
     @UsageSetting
     private final int mUsageSetting;
 
+    /**
+     * Subscription's transfer status
+     */
+    private final int mTransferStatus;
+
     // Below are the fields that do not exist in the database.
 
     /**
@@ -393,6 +399,7 @@
         this.mUsageSetting = usageSetting;
         this.mIsOnlyNonTerrestrialNetwork = false;
         this.mServiceCapabilities = 0;
+        this.mTransferStatus = SubscriptionManager.TRANSFER_STATUS_NONE;
     }
 
     /**
@@ -433,6 +440,7 @@
         this.mUsageSetting = builder.mUsageSetting;
         this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
         this.mServiceCapabilities = builder.mServiceCapabilities;
+        this.mTransferStatus = builder.mTransferStatus;
     }
 
     /**
@@ -928,6 +936,19 @@
         return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
     }
 
+    /**
+     * Get the transfer status for this subscription.
+     *
+     * @return The transfer status for this subscription.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public @TransferStatus int getTransferStatus() {
+        return mTransferStatus;
+    }
+
     @NonNull
     public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
             new Parcelable.Creator<SubscriptionInfo>() {
@@ -967,6 +988,7 @@
                     .setOnlyNonTerrestrialNetwork(source.readBoolean())
                     .setServiceCapabilities(
                             SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
+                    .setTransferStatus(source.readInt())
                     .build();
         }
 
@@ -1010,6 +1032,7 @@
         dest.writeInt(mUsageSetting);
         dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
         dest.writeInt(mServiceCapabilities);
+        dest.writeInt(mTransferStatus);
     }
 
     @Override
@@ -1075,6 +1098,7 @@
                 + " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
                 mServiceCapabilities).toString()
+                + " transferStatus=" + mTransferStatus
                 + "]";
     }
 
@@ -1101,7 +1125,8 @@
                 that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
                 && mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
                 && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
-                && mServiceCapabilities == that.mServiceCapabilities;
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus;
     }
 
     @Override
@@ -1110,7 +1135,8 @@
                 mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
                 mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
                 mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
-                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
+                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities,
+                mTransferStatus);
         result = 31 * result + Arrays.hashCode(mEhplmns);
         result = 31 * result + Arrays.hashCode(mHplmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1314,6 +1340,8 @@
          */
         private boolean mIsOnlyNonTerrestrialNetwork = false;
 
+        private int mTransferStatus = SubscriptionManager.TRANSFER_STATUS_NONE;
+
         /**
          * Service capabilities bitmasks the subscription supports.
          */
@@ -1363,6 +1391,7 @@
             mUsageSetting = info.mUsageSetting;
             mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
             mServiceCapabilities = info.mServiceCapabilities;
+            mTransferStatus = info.mTransferStatus;
         }
 
         /**
@@ -1785,6 +1814,18 @@
             mServiceCapabilities = combinedCapabilities;
             return this;
         }
+         /**
+         * Set subscription's transfer status
+         *
+         * @param status Subscription's transfer status
+         * @return The builder.
+         */
+        @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+        @NonNull
+        public Builder setTransferStatus(@TransferStatus int status) {
+            mTransferStatus = status;
+            return this;
+        }
 
         /**
          * Build the {@link SubscriptionInfo}.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index eb7e67d..3fde3b6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1148,6 +1148,14 @@
      */
     public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
 
+    /**
+     * TelephonyProvider column name to identify eSIM's transfer status.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
@@ -1453,6 +1461,41 @@
     private static final LruCache<Pair<String, Configuration>, Resources> sResourcesCache =
             new LruCache<>(MAX_RESOURCE_CACHE_ENTRY_COUNT);
 
+
+    /**
+     * The profile has not been transferred or converted to an eSIM.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_NONE = 0;
+
+    /**
+     * The existing profile of the old device has been transferred to an eSIM of the new device.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+
+    /**
+     * The existing profile of the same device has been converted to an eSIM of the same device
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_CONVERTED = 2;
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TRANSFER_STATUS"},
+            value = {
+                    TRANSFER_STATUS_NONE,
+                    TRANSFER_STATUS_TRANSFERRED_OUT,
+                    TRANSFER_STATUS_CONVERTED,
+            })
+    public @interface TransferStatus {}
+
+
     /**
      * A listener class for monitoring changes to {@link SubscriptionInfo} records.
      * <p>
@@ -1927,34 +1970,25 @@
      * Then for SDK 35+, if the caller identity is personal profile, then this will return
      * subscription 1 only and vice versa.
      *
-     * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
-     * {@link SubscriptionInfo#getSubscriptionId}.
+     * <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+     * {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will
+     * never return null.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see
      * {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
-     * <ul>
-     * <li>
-     * If null is returned the current state is unknown but if a {@link OnSubscriptionsChangedListener}
-     * has been registered {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be
-     * invoked in the future.
-     * </li>
-     * <li>
-     * If the list is empty then there are no {@link SubscriptionInfo} records currently available.
-     * </li>
-     * <li>
-     * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
-     * then by {@link SubscriptionInfo#getSubscriptionId}.
-     * </li>
-     * </ul>
+     * @return a list of the active {@link SubscriptionInfo} that is visible to the caller. If
+     *         an empty list or null is returned, then there are no active subscriptions that
+     *         are visible to the caller. If the number of active subscriptions available to
+     *         any caller changes, then this change will be indicated by
+     *         {@link OnSubscriptionsChangedListener#onSubscriptionsChanged}.
      *
      * @throws UnsupportedOperationException If the device does not have
-     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     *         {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
+    public @Nullable List<SubscriptionInfo> getActiveSubscriptionInfoList() {
         List<SubscriptionInfo> activeList = null;
 
         try {
@@ -1970,6 +2004,8 @@
         if (activeList != null) {
             activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
                     .collect(Collectors.toList());
+        } else {
+            activeList = Collections.emptyList();
         }
         return activeList;
     }
@@ -1998,12 +2034,7 @@
      *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     public @NonNull List<SubscriptionInfo> getCompleteActiveSubscriptionInfoList() {
-        List<SubscriptionInfo> completeList = getActiveSubscriptionInfoList(
-                /* userVisibleonly */false);
-        if (completeList == null) {
-            completeList = new ArrayList<>();
-        }
-        return completeList;
+        return getActiveSubscriptionInfoList(/* userVisibleonly */ false);
     }
 
     /**
@@ -2032,7 +2063,7 @@
     *
     * @hide
     */
-    public @Nullable List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+    public @NonNull List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
         List<SubscriptionInfo> activeList = null;
 
         try {
@@ -2045,11 +2076,13 @@
             // ignore it
         }
 
-        if (!userVisibleOnly || activeList == null) {
-            return activeList;
-        } else {
+        if (activeList == null || activeList.isEmpty()) {
+            return Collections.emptyList();
+        } else if (userVisibleOnly) {
             return activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
                     .collect(Collectors.toList());
+        } else {
+            return activeList;
         }
     }
 
@@ -2086,7 +2119,7 @@
      * @hide
      */
     @SystemApi
-    public List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
+    public @Nullable List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
         List<SubscriptionInfo> result = null;
 
         try {
@@ -2098,7 +2131,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return result;
+        return (result == null) ? Collections.emptyList() : result;
     }
 
     /**
@@ -2128,7 +2161,7 @@
      * @throws UnsupportedOperationException If the device does not have
      *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
-    public List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
+    public @Nullable List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
         List<SubscriptionInfo> result = null;
 
         try {
@@ -2139,7 +2172,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return result;
+        return (result == null) ? Collections.emptyList() : result;
     }
 
     /**
@@ -4726,4 +4759,27 @@
     public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
         return 1 << (capability - 1);
     }
+
+    /**
+     * Set the transfer status of the subscriptionInfo of the subId.
+     * @param subscriptionId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change.
+     *
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setTransferStatus(int subscriptionId, @TransferStatus int status) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                iSub.setTransferStatus(subscriptionId, status);
+            }
+        } catch (RemoteException ex) {
+            logd("setTransferStatus for subId = " + subscriptionId + " failed.");
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 89661a4..61c7a42 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18709,9 +18709,9 @@
      */
     @SystemApi
     @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
-    public static final class EmergencyCallDiagnosticParams {
+    public static final class EmergencyCallDiagnosticData {
         public static final class Builder {
-            private boolean mCollectTelecomDumpSys;
+            private boolean mCollectTelecomDumpsys;
             private boolean mCollectTelephonyDumpsys;
 
             // If this is set to a value other than -1L, then the logcat collection is enabled.
@@ -18724,9 +18724,9 @@
              * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected.
              * @return Builder instance corresponding to the configured call diagnostic params.
              */
-            public @NonNull Builder setTelecomDumpSysCollectionEnabled(
+            public @NonNull Builder setTelecomDumpsysCollectionEnabled(
                     boolean collectTelecomDumpsys) {
-                mCollectTelecomDumpSys = collectTelecomDumpsys;
+                mCollectTelecomDumpsys = collectTelecomDumpsys;
                 return this;
             }
 
@@ -18735,7 +18735,7 @@
              * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected.
              * @return Builder instance corresponding to the configured call diagnostic params.
              */
-            public @NonNull Builder setTelephonyDumpSysCollectionEnabled(
+            public @NonNull Builder setTelephonyDumpsysCollectionEnabled(
                     boolean collectTelephonyDumpsys) {
                 mCollectTelephonyDumpsys = collectTelephonyDumpsys;
                 return this;
@@ -18753,35 +18753,35 @@
             }
 
             /**
-             * Build the EmergencyCallDiagnosticParams from the provided Builder config.
-             * @return {@link EmergencyCallDiagnosticParams} instance from provided builder.
+             * Build the EmergencyCallDiagnosticData from the provided Builder config.
+             * @return {@link EmergencyCallDiagnosticData} instance from provided builder.
              */
-            public @NonNull EmergencyCallDiagnosticParams build() {
-                return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys,
+            public @NonNull EmergencyCallDiagnosticData build() {
+                return new EmergencyCallDiagnosticData(mCollectTelecomDumpsys,
                         mCollectTelephonyDumpsys, mLogcatStartTimeMillis);
             }
         }
 
-        private boolean mCollectTelecomDumpSys;
+        private boolean mCollectTelecomDumpsys;
         private boolean mCollectTelephonyDumpsys;
         private boolean mCollectLogcat;
         private long mLogcatStartTimeMillis;
 
         private static long sUnsetLogcatStartTime = -1L;
 
-        private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys,
+        private EmergencyCallDiagnosticData(boolean collectTelecomDumpsys,
                 boolean collectTelephonyDumpsys, long logcatStartTimeMillis) {
-            mCollectTelecomDumpSys = collectTelecomDumpSys;
+            mCollectTelecomDumpsys = collectTelecomDumpsys;
             mCollectTelephonyDumpsys = collectTelephonyDumpsys;
             mLogcatStartTimeMillis = logcatStartTimeMillis;
             mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime;
         }
 
-        public boolean isTelecomDumpSysCollectionEnabled() {
-            return mCollectTelecomDumpSys;
+        public boolean isTelecomDumpsysCollectionEnabled() {
+            return mCollectTelecomDumpsys;
         }
 
-        public boolean isTelephonyDumpSysCollectionEnabled() {
+        public boolean isTelephonyDumpsysCollectionEnabled() {
             return mCollectTelephonyDumpsys;
         }
 
@@ -18796,12 +18796,12 @@
 
         @Override
         public String toString() {
-            return "EmergencyCallDiagnosticParams{" +
-                    "mCollectTelecomDumpSys=" + mCollectTelecomDumpSys +
-                    ", mCollectTelephonyDumpsys=" + mCollectTelephonyDumpsys +
-                    ", mCollectLogcat=" + mCollectLogcat +
-                    ", mLogcatStartTimeMillis=" + mLogcatStartTimeMillis +
-                    '}';
+            return "EmergencyCallDiagnosticData{"
+                    + "mCollectTelecomDumpsys=" + mCollectTelecomDumpsys
+                    + ", mCollectTelephonyDumpsys=" + mCollectTelephonyDumpsys
+                    + ", mCollectLogcat=" + mCollectLogcat
+                    + ", mLogcatStartTimeMillis=" + mLogcatStartTimeMillis
+                    + '}';
         }
     }
 
@@ -18809,7 +18809,7 @@
      * Request telephony to persist state for debugging emergency call failures.
      *
      * @param dropboxTag Tag to use when persisting data to dropbox service.
-     * @param params Parameters controlling what is collected.
+     * @param data Parameters controlling what is collected in the diagnostics.
      *
      * @hide
      */
@@ -18817,7 +18817,7 @@
     @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
-            @NonNull EmergencyCallDiagnosticParams params) {
+            @NonNull EmergencyCallDiagnosticData data) {
         try {
             ITelephony telephony = ITelephony.Stub.asInterface(
                     TelephonyFrameworkInitializer
@@ -18826,10 +18826,10 @@
                             .get());
             if (telephony != null) {
                 telephony.persistEmergencyCallDiagnosticData(dropboxTag,
-                        params.isLogcatCollectionEnabled(),
-                        params.getLogcatCollectionStartTimeMillis(),
-                        params.isTelecomDumpSysCollectionEnabled(),
-                        params.isTelephonyDumpSysCollectionEnabled());
+                        data.isLogcatCollectionEnabled(),
+                        data.getLogcatCollectionStartTimeMillis(),
+                        data.isTelecomDumpsysCollectionEnabled(),
+                        data.isTelephonyDumpsysCollectionEnabled());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error while persistEmergencyCallDiagnosticData: " + e);
@@ -19207,13 +19207,11 @@
     /**
      * Returns whether the domain selection service is supported.
      *
-     * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}.
-     *
      * @return {@code true} if the domain selection service is supported.
      * @hide
      */
-    @TestApi
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean isDomainSelectionSupported() {
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
index 04752e4..0f5dd27 100644
--- a/telephony/java/android/telephony/TransportSelectorCallback.java
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -16,18 +16,28 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.telephony.Annotation.DisconnectCauses;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.function.Consumer;
 
 /**
- * A callback class used to receive the transport selection result.
+ * A callback class used by the domain selection module to notify the framework of the result of
+ * selecting a domain for a call.
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
 public interface TransportSelectorCallback {
     /**
      * Notify that {@link DomainSelector} instance has been created for the selection request.
+     * <p>
+     * DomainSelector callbacks run using the executor specified in
+     * {@link DomainSelectionService#onCreateExecutor}.
      *
      * @param selector the {@link DomainSelector} instance created.
      */
@@ -41,16 +51,13 @@
     void onWlanSelected(boolean useEmergencyPdn);
 
     /**
-     * Notify that WWAN transport has been selected.
-     */
-    @NonNull WwanSelectorCallback onWwanSelected();
-
-    /**
-     * Notify that WWAN transport has been selected.
+     * Notify that WWAN transport has been selected and the next phase of selecting
+     * the PS or CS domain is starting.
      *
-     * @param consumer The callback to receive the result.
+     * @param consumer The callback used by the {@link DomainSelectionService} to optionally run
+     *        emergency network scans and notify the framework of the WWAN transport result.
      */
-    void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+    void onWwanSelected(@NonNull Consumer<WwanSelectorCallback> consumer);
 
     /**
      * Notify that selection has terminated because there is no decision that can be made
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index f9c2620..ea83815 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -16,29 +16,38 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.CancellationSignal;
 import android.telephony.DomainSelectionService.EmergencyScanType;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.List;
 import java.util.function.Consumer;
 
 /**
- * A callback class used to receive the domain selection result.
+ * A callback class used to communicate with the framework to request network scans
+ * and notify the framework when a WWAN domain has been selected.
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
 public interface WwanSelectorCallback {
     /**
      * Notify the framework that the {@link DomainSelectionService} has requested an emergency
      * network scan as part of selection.
      *
-     * @param preferredNetworks the ordered list of preferred networks to scan.
-     * @param scanType indicates the scan preference, such as full service or limited service.
-     * @param signal notifies when the operation is canceled.
-     * @param consumer the handler of the response.
+     * @param preferredNetworks The ordered list of preferred networks to scan.
+     * @param scanType Indicates the scan preference, such as full service or limited service.
+     * @param resetScan Indicates that the previous scan result shall be reset before scanning.
+     * @param signal Notifies when the operation is canceled.
+     * @param consumer The handler of the response, which will contain a one-shot result
+     *        of the network scan.
      */
     void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
-            @EmergencyScanType int scanType,
+            @EmergencyScanType int scanType, boolean resetScan,
             @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
 
     /**
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 9b8e62f..0fe43b3 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -16,6 +16,7 @@
 package android.telephony.euicc;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -50,6 +51,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -1707,4 +1709,57 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the supported carrier ids for pSIM conversion.
+     *
+     * <p>Any existing pSIM conversion supported carrier list will be replaced
+     * by the {@code carrierIds} set here.
+     *
+     * @param carrierIds is a list of carrierIds that supports pSIM conversion
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+     * @throws IllegalStateException if this method is called when {@link #isEnabled} is false.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setPsimConversionSupportedCarriers(@NonNull Set<Integer> carrierIds) {
+        if (!isEnabled()) {
+            throw new IllegalStateException("Euicc is not enabled");
+        }
+        try {
+            int[] arr = carrierIds.stream().mapToInt(Integer::intValue).toArray();
+            getIEuiccController().setPsimConversionSupportedCarriers(arr);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Returns whether the given carrier id supports pSIM conversion or not.
+     *
+     * @param carrierId to check whether pSIM conversion is supported or not
+     * @return whether the given carrier id supports pSIM conversion or not,
+     *         or false if {@link #isEnabled} is false
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public boolean isPsimConversionSupported(int carrierId) {
+        if (!isEnabled()) {
+            return false;
+        }
+        try {
+            return getIEuiccController().isPsimConversionSupported(carrierId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelector.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index d94840b..0eeadee7 100644
--- a/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -19,7 +19,6 @@
 import android.telephony.DomainSelectionService.SelectionAttributes;
 
 oneway interface IDomainSelector {
-    void cancelSelection();
     void reselectDomain(in SelectionAttributes attr);
     void finishSelection();
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 3f41d56..cc770aa 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -376,4 +376,12 @@
      * @param data with the sim specific configs to be backed up.
      */
     void restoreAllSimSpecificSettingsFromBackup(in byte[] data);
+
+    /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     * @param subId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change. This value must be one of the following.
+     */
+    @EnforcePermission("WRITE_EMBEDDED_SUBSCRIPTIONS")
+    void setTransferStatus(int subId, int status);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a1fc064..213fbc5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3203,6 +3203,18 @@
     boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode);
 
     /**
+     *  @return {@code true} if the DomainSelectionService is set,
+     *          {@code false} otherwise.
+     */
+    boolean setDomainSelectionServiceOverride(in ComponentName componentName);
+
+    /**
+     *  @return {@code true} if the DomainSelectionService override is cleared,
+     *          {@code false} otherwise.
+     */
+    boolean clearDomainSelectionServiceOverride();
+
+    /**
      * Enable or disable notifications sent for cellular identifier disclosure events.
      *
      * Disclosure events are defined as instances where a device has sent a cellular identifier
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
index 6777256d..26daacd 100644
--- a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -20,10 +20,9 @@
 import com.android.internal.telephony.ITransportSelectorResultCallback;
 import com.android.internal.telephony.IWwanSelectorCallback;
 
-interface ITransportSelectorCallback {
-    oneway void onCreated(in IDomainSelector selector);
-    oneway void onWlanSelected(boolean useEmergencyPdn);
-    IWwanSelectorCallback onWwanSelected();
-    oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
-    oneway void onSelectionTerminated(int cause);
+oneway interface ITransportSelectorCallback {
+    void onCreated(in IDomainSelector selector);
+    void onWlanSelected(boolean useEmergencyPdn);
+    void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+    void onSelectionTerminated(int cause);
 }
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 94e7c87..87955ac 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -20,7 +20,7 @@
 
 oneway interface IWwanSelectorCallback {
     void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
-            int scanType, in IWwanSelectorResultCallback cb);
+            int scanType, boolean resetScan, in IWwanSelectorResultCallback cb);
     void onDomainSelected(int domain, boolean useEmergencyPdn);
     void onCancel();
 }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 19f1a5b..d417772 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -57,4 +57,6 @@
     boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
     boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage);
     boolean isCompatChangeEnabled(String callingPackage, long changeId);
+    void setPsimConversionSupportedCarriers(in int[] carrierIds);
+    boolean isPsimConversionSupported(in int carrierId);
 }
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index cf2d5d6..d17cd1f 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_input_framework",
     // 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"
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index 5434c82..5f1bc87 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -67,8 +67,14 @@
         assertEquals("keyCharacterMap not equal", keyCharacterMap, outKeyCharacterMap);
 
         for (int j = 0; j < device.getMotionRanges().size(); j++) {
-            assertMotionRangeEquals(device.getMotionRanges().get(j),
-                    outDevice.getMotionRanges().get(j));
+            InputDevice.MotionRange motionRange = device.getMotionRanges().get(j);
+            assertMotionRangeEquals(motionRange, outDevice.getMotionRanges().get(j));
+
+            int axis = motionRange.getAxis();
+            int source = motionRange.getSource();
+            assertEquals(
+                    device.getViewBehavior().shouldSmoothScroll(axis, source),
+                    outDevice.getViewBehavior().shouldSmoothScroll(axis, source));
         }
     }
 
@@ -93,7 +99,8 @@
                 .setHasBattery(true)
                 .setKeyboardLanguageTag("en-US")
                 .setKeyboardLayoutType("qwerty")
-                .setUsiVersion(new HostUsiVersion(2, 0));
+                .setUsiVersion(new HostUsiVersion(2, 0))
+                .setShouldSmoothScroll(true);
 
         for (int i = 0; i < 30; i++) {
             deviceBuilder.addMotionRange(
diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp
index 58ceb3f..5ed8d8d 100644
--- a/tests/InputMethodStressTest/Android.bp
+++ b/tests/InputMethodStressTest/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_method_framework",
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 23efe54..2751141 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // 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"
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index ddb3649..12d4338 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     // 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"
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
index ed34fa9..0f21035 100644
--- a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     // 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"
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
index 0bdb3a8..38355530 100644
--- a/tests/UpdatableSystemFontTest/testdata/Android.bp
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     // 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"
diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h
index 5d3abc6..9276402 100644
--- a/tools/aapt/ApkBuilder.h
+++ b/tools/aapt/ApkBuilder.h
@@ -44,7 +44,7 @@
     android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs);
 
     /**
-     * Adds a file to be written to the final APK. It's name must not collide
+     * Adds a file to be written to the final APK. Its name must not collide
      * with that of any files previously added. When a Split APK is being
      * generated, duplicates can exist as long as they are in different splits
      * (resources.arsc, AndroidManifest.xml).
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 2f2ef92..66a0510 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -187,7 +187,7 @@
         "       be loaded alongside the base APK at runtime.\n"
         "   --feature-of\n"
         "       Builds a split APK that is a feature of the apk specified here. Resources\n"
-        "       in the base APK can be referenced from the the feature APK.\n"
+        "       in the base APK can be referenced from the feature APK.\n"
         "   --feature-after\n"
         "       An app can have multiple Feature Split APKs which must be totally ordered.\n"
         "       If --feature-of is specified, this flag specifies which Feature Split APK\n"
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 938a5ed..b054a57 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -262,6 +262,23 @@
         "$(genDir)/aapt2_tests " +
         "--gtest_output=xml:$(out) " +
         ">/dev/null 2>&1 ; true",
+    dist: {
+        targets: ["aapt2_run_host_unit_tests"],
+        dir: "gtest",
+        dest: "aapt2_host_unit_tests_result.xml",
+    },
+    arch: {
+        x86: {
+            dist: {
+                suffix: "_x86",
+            },
+        },
+        x86_64: {
+            dist: {
+                suffix: "_x86_64",
+            },
+        },
+    },
 }
 
 phony_rule {
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index a1d0389..cc3a156 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -238,7 +238,7 @@
                     } else if (classBounds.isDataclass) {
 
                         // Insert placeholder for generated code to be inserted for the 1st time
-                        chunks.last = (chunks.last as Code)
+                        chunks[chunks.lastIndex] = (chunks.last() as Code)
                                 .lines
                                 .dropLastWhile { it.isBlank() }
                                 .run {
@@ -286,4 +286,4 @@
                     .let { addAll(it) }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 9ceb204..a40bdd7 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -137,14 +137,4 @@
             cause)
 }
 
-var <T> MutableList<T>.last
-    get() = last()
-    set(value) {
-        if (isEmpty()) {
-            add(value)
-        } else {
-            this[size - 1] = value
-        }
-    }
-
-inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)
\ No newline at end of file
+inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)