Merge "Make sure to stop the background services even if it's never active" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index e6e835b..9ee74e3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -24,6 +24,7 @@
     ":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}",
@@ -35,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}",
@@ -68,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}",
@@ -1137,3 +1140,22 @@
     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 ef64a89..27c372a 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -223,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",
     ],
 }
@@ -232,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",
     ],
 }
@@ -241,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",
     ],
 }
@@ -250,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 7ee4319..e8fcf4b 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -221,7 +221,7 @@
     name: "offline-sdk-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc offline",
@@ -242,7 +242,7 @@
     name: "offline-sdk-referenceonly-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc offline",
@@ -286,7 +286,7 @@
     name: "ds-docs-java",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
@@ -320,7 +320,7 @@
 droiddoc {
     name: "ds-docs-kt",
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     flags: [
         "-noJdkLink",
@@ -374,7 +374,7 @@
     name: "ds-static-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
@@ -391,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/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 04f907a..2e3fe5b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -506,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
@@ -1796,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
@@ -4378,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();
@@ -5418,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);
@@ -9348,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;
   }
 
@@ -9514,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);
@@ -12398,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);
@@ -12412,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);
@@ -13841,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
@@ -19153,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);
@@ -27284,6 +27305,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
@@ -27435,8 +27474,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);
   }
 
@@ -27620,6 +27661,16 @@
 
   @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 unregisterCallback(@NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    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 {
@@ -27639,17 +27690,26 @@
 
   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 {
@@ -27666,12 +27726,26 @@
     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);
   }
 
 }
@@ -42366,6 +42440,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);
@@ -43533,13 +43609,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 {
@@ -45222,13 +45329,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();
@@ -53870,6 +53977,7 @@
     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";
@@ -59599,7 +59707,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 37be5c7..1273da7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -350,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 {
@@ -384,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();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f026b57..f36d560 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";
@@ -3944,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 {
@@ -3991,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();
@@ -4043,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
@@ -4392,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();
@@ -4401,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();
@@ -4410,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);
@@ -4581,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);
@@ -10063,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);
@@ -10076,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);
@@ -10282,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";
@@ -10291,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";
   }
@@ -11061,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
@@ -14067,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();
@@ -14337,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
@@ -14584,7 +14766,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);
@@ -14819,6 +15001,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();
@@ -14838,7 +15021,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);
@@ -15017,19 +15200,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 {
@@ -15064,6 +15247,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();
@@ -15119,6 +15309,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 {
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/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 4c54b03..2c00c99 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -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/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/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/Notification.java b/core/java/android/app/Notification.java
index 8883907..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.
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/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/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/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
index 074ce9b..5e8bdb5 100644
--- a/core/java/android/app/wearable/flags.aconfig
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -5,4 +5,25 @@
     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/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 5df23c0..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();
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index bc29f8b..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
@@ -6309,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/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 19bce0b..f31521d 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -199,3 +199,10 @@
     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 d7e64b6..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"
@@ -122,3 +136,10 @@
     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/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/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/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 e8d5d37..1837976 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -17,6 +17,7 @@
 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;
@@ -24,11 +25,16 @@
 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;
 
@@ -42,12 +48,14 @@
 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.
             @Nullable
@@ -60,10 +68,10 @@
 
         Intent intent;
         if (enabledProviderDataList != null) {
-            intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
+            intent = createCredentialSelectorIntent(context, requestInfo, enabledProviderDataList,
                     disabledProviderDataList, resultReceiver);
         } else {
-            intent = createCredentialSelectorIntent(requestInfo,
+            intent = createCredentialSelectorIntent(context, requestInfo,
                     disabledProviderDataList, resultReceiver);
         }
         intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
@@ -77,7 +85,8 @@
      * @hide
      */
     @NonNull
-    public static Intent createCredentialSelectorIntent(
+    private static Intent createCredentialSelectorIntent(
+            @NonNull Context context,
             @NonNull RequestInfo requestInfo,
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
             @NonNull
@@ -90,6 +99,10 @@
                                 .getString(
                                         com.android.internal.R.string
                                                 .config_credentialManagerDialogComponent));
+        ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
+        if (oemOverrideComponentName != null) {
+            componentName = oemOverrideComponentName;
+        }
         intent.setComponent(componentName);
         intent.putParcelableArrayListExtra(
                 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
@@ -100,9 +113,73 @@
         return intent;
     }
 
-    /** Generate a new launch intent to the Credential Selector UI. */
+    /**
+     * 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.
+     *
+     * @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
@@ -111,7 +188,7 @@
             @NonNull
             ArrayList<DisabledProviderData> disabledProviderDataList,
             @NonNull ResultReceiver resultReceiver) {
-        Intent intent = createCredentialSelectorIntent(requestInfo,
+        Intent intent = createCredentialSelectorIntent(context, requestInfo,
                 disabledProviderDataList, resultReceiver);
         intent.putParcelableArrayListExtra(
                 ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
@@ -120,8 +197,6 @@
 
     /**
      * Creates an Intent that cancels any UI matching the given request token id.
-     *
-     * @hide
      */
     @NonNull
     public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
@@ -134,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;
     }
 
@@ -155,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/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/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/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 db992cd..744dfae9 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -28,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;
@@ -857,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/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/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/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/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/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/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/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/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/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f3e0ea7..a49aee1 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -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/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
index 82559da..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,23 +23,17 @@
 import android.tracing.perfetto.StopCallbackArguments;
 import android.util.proto.ProtoInputStream;
 
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
 /**
  * @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;
     private final Runnable mOnFlushStaticCallback;
     private final Runnable mOnStopStaticCallback;
 
-    private final ConcurrentHashMap<Integer, ConcurrentHashMap<String, Integer>> mHandlerMappings =
-            new ConcurrentHashMap<>();
-
     public TransitionDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
         super(DATA_SOURCE_NAME);
         this.mOnStartStaticCallback = onStart;
@@ -49,20 +42,6 @@
     }
 
     @Override
-    protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
-        return new TlsState(args.getDataSourceInstanceLocked().getInstanceIndex());
-    }
-
-    public class TlsState {
-        public final Map<String, Integer> handlerMapping;
-
-        public TlsState(int instanceIndex) {
-            handlerMapping = mHandlerMappings
-                    .computeIfAbsent(instanceIndex, index -> new ConcurrentHashMap<>());
-        }
-    }
-
-    @Override
     public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
         return new DataSourceInstance(this, instanceIndex) {
             @Override
@@ -78,7 +57,6 @@
             @Override
             protected void onStop(StopCallbackArguments args) {
                 mOnStopStaticCallback.run();
-                mHandlerMappings.remove(instanceIndex);
             }
         };
     }
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/InputDevice.java b/core/java/android/view/InputDevice.java
index 891e2a2..d22d2a5 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1544,7 +1544,7 @@
          * 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
+         * @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
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 2366ff7..5c5817f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15868,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) {
@@ -15900,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);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c27b2b1..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,9 @@
     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
@@ -4080,7 +4086,6 @@
         setPreferredFrameRate(mPreferredFrameRate);
         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-        mPreferredFrameRate = 0;
     }
 
     private void createSyncIfNeeded() {
@@ -6134,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
@@ -6445,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;
@@ -6472,6 +6479,10 @@
                     }
                     updatePointerIcon(mPointerIconEvent);
                     break;
+                case MSG_FRAME_RATE_SETTING:
+                    mPreferredFrameRate = 0;
+                    setPreferredFrameRate(mPreferredFrameRate);
+                    break;
             }
         }
     }
@@ -7482,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);
             }
             /**
@@ -7490,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,
@@ -11539,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;
@@ -11584,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.
@@ -12234,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) {
@@ -12263,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);
         }
     }
 
@@ -12285,7 +12312,7 @@
 
     private boolean shouldSetFrameRate() {
         // use toolkitSetFrameRate flag to gate the change
-        return mPreferredFrameRate > 0 && sToolkitSetFrameRateReadOnlyFlagValue;
+        return sToolkitSetFrameRateReadOnlyFlagValue;
     }
 
     private boolean shouldTouchBoost(int motionEventAction, int windowType) {
@@ -12336,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 38cf490..ac2a66e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1472,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.
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 146b576..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();
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/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/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_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 2e20cce..bc63881 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -58,6 +58,14 @@
 
 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: "293642394"
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/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/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_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/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 6be1be4..c71a842 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5591,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" />
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/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/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/symbols.xml b/core/res/res/values/symbols.xml
index 603b902..4cf37df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3146,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" />
@@ -5346,4 +5347,6 @@
   <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 1b25d7f..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: [
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/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/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/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 1ad71da..3237812 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -126,7 +126,7 @@
     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(RavenwoodRule.isOnRavenwood());
+        Assume.assumeTrue(false); // TODO: re-enable
         mThrown.expect(IllegalStateException.class);
 
         final HandlerThread thread = new HandlerThread("HandlerThreadTest");
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/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 60769c7..52e996c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -20,6 +20,7 @@
 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;
@@ -669,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);
@@ -796,6 +802,33 @@
     }
 
     /**
+     * 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.
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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 91e620c..0baaff0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
         <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>
 
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/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 50a58da..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;
@@ -466,6 +468,7 @@
      * Call when all the views should be removed/cleaned up.
      */
     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 a43a951..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;
@@ -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;
@@ -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;
 
@@ -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
@@ -1715,6 +1742,10 @@
         public void removeBubble(Bubble removedBubble) {
             if (mLayerView != null) {
                 mLayerView.removeBubble(removedBubble);
+                if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+                    mLayerView.setVisibility(INVISIBLE);
+                    removeFromWindowManagerMaybe();
+                }
             }
         }
 
@@ -1769,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();
 
@@ -1974,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 dbfa260..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
@@ -16,10 +16,9 @@
 package com.android.wm.shell.bubbles;
 
 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;
@@ -36,6 +35,7 @@
 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;
@@ -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();
     }
@@ -430,12 +423,13 @@
      * 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;
         }
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 dc27133..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;
@@ -35,6 +35,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.taskview.TaskView;
 
 /**
@@ -79,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;
@@ -99,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);
@@ -159,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;
 
@@ -178,10 +172,8 @@
 
         @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);
             }
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/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b95d258..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
@@ -273,6 +273,9 @@
             if (endAction != null) {
                 endAction.run();
             }
+            if (mBubbleData.getBubbles().isEmpty()) {
+                mBubbleController.onAllBubblesAnimatedOut();
+            }
         };
         if (mDragController != null && mDragController.isStuckToDismiss()) {
             mAnimationHelper.animateDismiss(runnable);
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/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/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/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/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/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 0c25f27..b3e8bd9 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
@@ -19,16 +19,15 @@
 import android.internal.perfetto.protos.PerfettoTrace;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.tracing.perfetto.DataSourceInstance;
 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;
 
@@ -41,6 +40,7 @@
             mActiveTraces::incrementAndGet,
             this::onFlush,
             mActiveTraces::decrementAndGet);
+    private final Map<String, Integer> mHandlerMapping = new HashMap<>();
 
     public PerfettoTransitionTracer() {
         Producer.init(InitArguments.DEFAULTS);
@@ -69,7 +69,7 @@
 
     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);
@@ -81,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;
     }
@@ -194,22 +193,14 @@
     }
 
     private void onFlush() {
-        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
-        try {
-            doOnFlush();
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
-    private void doOnFlush() {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
-            final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
-            for (String handler : handlerMapping.keySet()) {
+            for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
+                final String handler = entry.getKey();
+                final int handlerId = entry.getValue();
                 final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
-                os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+                os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
                 os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
                 os.end(token);
             }
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/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/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/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/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/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 672f58b..aed3e60 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -556,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";
 
 
@@ -657,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";
 
     /**
@@ -671,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";
 
     /**
@@ -682,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";
 
     /**
@@ -692,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";
 
     /**
@@ -704,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";
 
@@ -715,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";
 
     /**
@@ -725,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";
 
     /**
@@ -735,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";
 
     /**
@@ -747,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";
 
 
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6b03041..432e109 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1262,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
@@ -1272,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);
                     }
                 }
             });
@@ -1441,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) {
         }
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index f373bed..15ce9fd 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -171,36 +171,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 +216,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 +225,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 +234,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 +243,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";
 
@@ -510,7 +502,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,
@@ -526,7 +517,6 @@
      * Unregisters the existing {@link TvAdServiceCallback}.
      *
      * @param callback The existing callback to remove.
-     * @hide
      */
     public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
         Preconditions.checkNotNull(callback);
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 953b5cf..d7b29d3 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -199,7 +199,6 @@
          *
          * @param enable {@code true} if you want to enable the media view. {@code false}
          *            otherwise.
-         * @hide
          */
         @CallSuper
         public void setMediaViewEnabled(final boolean enable) {
@@ -225,7 +224,6 @@
          * Returns {@code true} if media view is enabled, {@code false} otherwise.
          *
          * @see #setMediaViewEnabled(boolean)
-         * @hide
          */
         public boolean isMediaViewEnabled() {
             return mMediaViewEnabled;
@@ -253,21 +251,18 @@
 
         /**
          * Starts TvAdService session.
-         * @hide
          */
         public void onStartAdService() {
         }
 
         /**
          * Stops TvAdService session.
-         * @hide
          */
         public void onStopAdService() {
         }
 
         /**
          * Resets TvAdService session.
-         * @hide
          */
         public void onResetAdService() {
         }
@@ -618,9 +613,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) {
@@ -636,7 +630,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) {
         }
@@ -646,7 +639,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() {
@@ -654,27 +646,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/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index be88506..ee01468 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -160,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) {
@@ -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) {
@@ -381,7 +378,6 @@
      * @param event The input event.
      * @return If you handled the event, return {@code true}. If you want to allow the event to be
      *         handled by the next receiver, return {@code false}.
-     * @hide
      */
     public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
         return false;
@@ -392,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,
@@ -407,7 +402,6 @@
      *
      * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
      * @see #clearOnUnhandledInputEventListener()
-     * @hide
      */
     @Nullable
     public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
@@ -416,7 +410,6 @@
 
     /**
      * Clears the {@link OnUnhandledInputEventListener}.
-     * @hide
      */
     public void clearOnUnhandledInputEventListener() {
         mOnUnhandledInputEventListener = null;
@@ -453,7 +446,6 @@
 
     /**
      * Starts the AD service.
-     * @hide
      */
     public void startAdService() {
         if (DEBUG) {
@@ -466,7 +458,6 @@
 
     /**
      * Stops the AD service.
-     * @hide
      */
     public void stopAdService() {
         if (DEBUG) {
@@ -481,7 +472,6 @@
      * Resets the AD service.
      *
      * <p>This releases the resources of the corresponding {@link TvAdService.Session}.
-     * @hide
      */
     public void resetAdService() {
         if (DEBUG) {
@@ -622,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/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 9742d46..845a8f9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -73,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();
@@ -98,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
@@ -231,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 f8b640c..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 55506a1..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());
     }
 
     /**
@@ -1205,6 +1208,22 @@
     }
 
     /**
+     * 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 isObserveModeEnabled() {
+        try {
+            return sService.isObserveModeEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
      * 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.
@@ -1785,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.
@@ -1816,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) {
@@ -1842,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)
@@ -2951,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/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 63e6c50..ab10b5a 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -1,13 +1,59 @@
-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",
     ],
+    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"],
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dffe4e2..5d03ef5 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -390,7 +390,8 @@
             return;
         }
 
-        CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.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/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index fdda9ea..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">
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index c7c2fda..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">
 
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/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 bd9d2e6..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
@@ -108,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 {
@@ -295,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 07f1fa3..2628f09 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -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")
@@ -153,7 +155,8 @@
                     return
                 }
 
-                val fillResponse = convertToFillResponse(result, request)
+                val fillResponse = convertToFillResponse(result, request,
+                    responseClientState)
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -260,7 +263,8 @@
 
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
-            filLRequest: FillRequest
+            filLRequest: FillRequest,
+            responseClientState: Bundle
     ): FillResponse? {
         val candidateProviders = getCredResponse.candidateProviderDataList
         if (candidateProviders.isEmpty()) {
@@ -281,6 +285,7 @@
         if (!validFillResponse) {
             return null
         }
+        fillResponseBuilder.setClientState(responseClientState)
         return fillResponseBuilder.build()
     }
 
@@ -313,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
@@ -578,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()
@@ -596,7 +602,8 @@
 
     private fun traverseStructure(
             structure: AssistStructure,
-            cmRequests: MutableList<CredentialOption>
+            cmRequests: MutableList<CredentialOption>,
+            responseClientState: Bundle
     ) {
         val windowNodes: List<AssistStructure.WindowNode> =
                 structure.run {
@@ -604,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)
         }
 
@@ -623,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()
@@ -637,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/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/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/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/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/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/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 2e64212..1092a16 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -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/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/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/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/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/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/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/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/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index e2eda4f..6d6e2ff 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,7 +32,5 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "truth",
-        "kotlinx_coroutines_test",
-        "mockito-target-minus-junit4",
     ],
 }
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/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 ba3026e..2ad7192 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -168,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 {
@@ -389,6 +392,13 @@
 }
 
 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."
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/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 071433e..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())
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/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/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/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/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/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/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 e4ce6cb..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,6 +15,8 @@
  */
 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
@@ -24,15 +26,19 @@
 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,
         )
     }
 
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 6ad32cc..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,37 +20,38 @@
 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.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
@@ -59,37 +60,18 @@
 
     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: SelectedComponentRepository
 
     @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
+    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
-
-        userRepository = kosmos.fakeUserRepository
-        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
-
-        whenever(controlsComponent.getControlsListingController())
-            .thenReturn(Optional.of(controlsListingController))
-
-        underTest =
-            HomeControlsComponentInteractor(
-                selectedComponentRepository,
-                controlsComponent,
-                authorizedPanelsRepository,
-                userRepository,
-                kosmos.applicationCoroutineScope,
-            )
-    }
+            underTest = homeControlsComponentInteractor
+        }
 
     @Test
     fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
@@ -181,22 +163,112 @@
                 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", hasPanel = hasPanelBoolean))
-        val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
+        val callback = withArgCaptor {
+            verify(kosmos.controlsListingController).addCallback(capture())
+        }
         callback.onServicesUpdated(listings)
     }
 
     private suspend fun TestScope.setActiveUser(user: UserInfo) {
-        userRepository.setSelectedUserInfo(user)
+        kosmos.fakeUserRepository.setSelectedUserInfo(user)
         kosmos.fakeUserTracker.set(listOf(user), 0)
         runCurrent()
     }
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/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/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/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 189ba7b..9d3f0d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,6 +265,7 @@
                 authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
                 windowController = mock(),
                 deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+                centralSurfaces = mock(),
             )
         startable.start()
 
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 12dbf11..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,6 +49,7 @@
 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
@@ -65,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
@@ -78,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
@@ -115,6 +119,7 @@
                 authenticationInteractor = dagger.Lazy { authenticationInteractor },
                 windowController = windowController,
                 deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+                centralSurfaces = centralSurfaces,
             )
     }
 
@@ -763,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,
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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 0cab10db..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
@@ -50,6 +64,9 @@
     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: ControlsListingController? =
@@ -115,8 +132,62 @@
             }
             .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
 
+    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/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/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/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 4766a84..8f08efa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -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:
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/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/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/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/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/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/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/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/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/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 56c0ca9..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,7 @@
 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
@@ -85,6 +87,7 @@
     private val authenticationInteractor: Lazy<AuthenticationInteractor>,
     private val windowController: NotificationShadeWindowController,
     private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+    private val centralSurfaces: CentralSurfaces,
 ) : CoreStartable {
 
     override fun start() {
@@ -95,6 +98,7 @@
             hydrateSystemUiState()
             collectFalsingSignals()
             hydrateWindowFocus()
+            hydrateInteractionState()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -376,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/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/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/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/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 5e0110b..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);
         }
 
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/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 11e374f..d4c180d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -18,25 +18,36 @@
 
 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;
 
@@ -45,44 +56,100 @@
             new MediaProjectionManager.Callback() {
                 @Override
                 public void onStart(MediaProjectionInfo info) {
-                    Trace.beginSection(
-                            "SNPC.onProjectionStart");
-                    // Only enable sensitive content protection if sharing full screen
-                    // Launch cookie only set (non-null) if sharing single app/task
-                    updateProjectionState((info.getLaunchCookie() == null) ? info : null);
-                    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");
-                    updateProjectionState(null);
-                    Trace.endSection();
-                }
-
-                private void updateProjectionState(MediaProjectionInfo info) {
-                    // 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);
+                    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);
         }
     }
 
@@ -96,11 +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
-        return mProjection != null;
+
+        return !mExemptPackages.contains(projection.getPackageName());
     }
 
     @Override
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/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/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/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/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/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/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/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 94ea799..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,8 +31,8 @@
 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
@@ -444,7 +444,7 @@
             userTracker,
             authorizedPanelsRepository,
             preferredPanelsRepository,
-            kosmos.packageChangeRepository,
+            kosmos.packageChangeInteractor,
             userManager,
             broadcastDispatcher,
         )
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/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 edba902..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
@@ -530,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/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 25a7eb8..9517f82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -46,6 +46,7 @@
 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
@@ -118,6 +119,11 @@
         initAndAttachContainerView()
     }
 
+    @After
+    fun tearDown() {
+        ViewUtils.detachView(parentView)
+    }
+
     @Test
     fun isEnabled_communalEnabled_returnsTrue() {
         communalRepository.setIsCommunalEnabled(true)
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 7f4508a..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;
@@ -202,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);
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 7bd9d92..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;
@@ -229,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();
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 5450537..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
@@ -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,57 +133,9 @@
                 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 =
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/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 9919c6b..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
@@ -17,88 +17,93 @@
 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.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)
 
         setShareFullScreen()
+        whenever(activityManager.bugreportWhitelistedPackages)
+            .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
 
-        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+        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()
     }
@@ -108,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()
@@ -117,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()
     }
@@ -131,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)
     }
@@ -150,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()
@@ -160,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)
@@ -175,24 +180,24 @@
 
     @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)
     }
@@ -200,7 +205,25 @@
     @Test
     fun isSensitiveStateActive_projectionActive_singleActivity_false() {
         setShareSingleApp()
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        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)
     }
@@ -215,7 +238,7 @@
     @Test
     fun shouldProtectNotification_projectionActive_singleActivity_false() {
         setShareSingleApp()
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
 
@@ -224,7 +247,7 @@
 
     @Test
     fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
 
@@ -233,7 +256,7 @@
 
     @Test
     fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
 
@@ -242,21 +265,43 @@
 
     @Test
     fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
-        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        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() {
-        `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
-        `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+        whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+        whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
     }
 
     private fun setShareSingleApp() {
-        `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
-        `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+        whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+        whenever(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
     }
 
     private fun setupNotificationEntry(
@@ -266,10 +311,10 @@
         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.packageName).thenReturn(packageName)
-        `when`(sbn.notification).thenReturn(notification)
-        `when`(notification.isFgsOrUij).thenReturn(isFgs)
+        whenever(notificationEntry.sbn).thenReturn(sbn)
+        whenever(sbn.packageName).thenReturn(packageName)
+        whenever(sbn.notification).thenReturn(notification)
+        whenever(notification.isFgsOrUij).thenReturn(isFgs)
 
         return notificationEntry
     }
@@ -282,5 +327,6 @@
         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 f3e9203..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();
@@ -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),
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/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/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 b9a3d38..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
@@ -84,6 +86,10 @@
     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/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/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/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/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/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 85fa65a..7b5932b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -58,6 +58,11 @@
     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.
      */
@@ -75,8 +80,10 @@
     }
 
     public static void init(RavenwoodRule rule) {
-        maybeThrowPendingUncaughtException(false);
-        Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+            maybeThrowPendingUncaughtException(false);
+            Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+        }
 
         RuntimeInit.redirectLogStreams();
 
@@ -129,7 +136,9 @@
         android.os.Binder.reset$ravenwood();
         android.os.Process.reset$ravenwood();
 
-        maybeThrowPendingUncaughtException(true);
+        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+            maybeThrowPendingUncaughtException(true);
+        }
     }
 
     public static void logTestRunner(String label, Description description) {
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 44144f8..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 {
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/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/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/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/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 797a2e6..a341b4a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1676,7 +1676,7 @@
         }
         byte[] certificateDigest = null;
         try {
-            certificateDigest = new Signature(certificateDigestStr).toByteArray();
+            certificateDigest = new Signature(certificateDigestStr.replace(":", "")).toByteArray();
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "<" + elementName + "> with invalid sha256-cert-digest in "
                     + permFile + " at " + parser.getPositionDescription());
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/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/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/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/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/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/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 e930627..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();
         }
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/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 fff4939..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
@@ -3732,6 +3747,44 @@
                             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,
@@ -3781,32 +3834,6 @@
                     + " cs=" + cs);
         }
 
-        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;
-        }
-
         final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
         final boolean startInputByWinGainedFocus =
@@ -5333,7 +5360,7 @@
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
                                         mSettings.getEnabledInputMethodList());
                     mSettings.putSelectedDefaultDeviceInputMethod(
-                            newDefaultIme == null ? "" : newDefaultIme.getId());
+                            newDefaultIme == null ? null : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
                 return true;
@@ -5376,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.
@@ -6559,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 638382e..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)));
         }
@@ -2873,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) {
@@ -2890,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;
@@ -2908,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);
                 }
             }
         });
@@ -4880,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;
                             }
@@ -4913,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.
          *
@@ -5044,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);
             }
@@ -5063,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);
             }
@@ -6529,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) {
@@ -6555,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);
@@ -10326,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;
@@ -10343,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) {
@@ -10362,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/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 0bbad85..dc97e5f 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -819,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);
@@ -844,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);
@@ -900,6 +901,7 @@
         return bitmap;
     }
 
+    @Nullable
     Bitmap includeCloudOverlay(Bitmap bitmap) {
         Drawable cloudDrawable =
                 mContext.getResources()
@@ -920,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 abea56b..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);
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 81f9d1b..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();
 
@@ -4934,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 b947aa3..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()) {
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/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/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/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 716aee3..e743172 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5150,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;
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/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/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/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 554cbce..57448cb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -346,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;
 
@@ -8629,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 {
@@ -9162,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;
@@ -9222,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 */
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f5806c0..68dade0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3020,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_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 e8b32cd..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());
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 1a9a0e6..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)),
+                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                        isShowAllOptionsRequested),
                 /*providerDataList=*/ null,
-                /*isRequestForAllOptions=*/ true);
+                /*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/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/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 897afbf..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"
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 42bcb33..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,9 @@
 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;
@@ -210,6 +213,10 @@
 
     private int mPreferredHdrOutputType;
 
+    private Handler mPowerHandler;
+
+    private UserManager mUserManager;
+
     private final DisplayManagerService.Injector mShortMockedInjector =
             new DisplayManagerService.Injector() {
                 @Override
@@ -370,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();
@@ -2789,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
@@ -2820,7 +2909,7 @@
             public void releaseSuspendBlocker(String id) {
 
             }
-        }, new Handler(Looper.getMainLooper()), mSensorManager);
+        }, mPowerHandler, mSensorManager);
     }
 
     private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
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 78ec2ff..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/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e7aaed4..75409d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -236,10 +236,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 +255,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
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/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/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/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/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/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 03cdbbd..eddff9ab 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -716,7 +716,8 @@
         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).toByteArray();
+        byte[] certificateDigest = new Signature(certificateDigestStr.replace(":", ""))
+                .toByteArray();
         String contents = "<config>"
                 + "<" + "enhanced-confirmation-trusted-installer" + " "
                 + "package=\"" + pkgName + "\""
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 8261dee..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();
     }
 
@@ -2332,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());
@@ -2350,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());
@@ -3427,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();
     }
@@ -4116,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));
@@ -4134,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);
@@ -9741,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 */,
@@ -11962,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(),
@@ -11989,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 =
@@ -12000,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/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 46e14d51..5f18f84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -208,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 0ee78e3..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);
         }
 
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/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/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index f8e8529..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.
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/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/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index eb7e67d..1749545 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1927,34 +1927,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 +1961,8 @@
         if (activeList != null) {
             activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
                     .collect(Collectors.toList());
+        } else {
+            activeList = Collections.emptyList();
         }
         return activeList;
     }
@@ -1998,12 +1991,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 +2020,7 @@
     *
     * @hide
     */
-    public @Nullable List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+    public @NonNull List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
         List<SubscriptionInfo> activeList = null;
 
         try {
@@ -2045,11 +2033,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 +2076,7 @@
      * @hide
      */
     @SystemApi
-    public List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
+    public @Nullable List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
         List<SubscriptionInfo> result = null;
 
         try {
@@ -2098,7 +2088,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return result;
+        return (result == null) ? Collections.emptyList() : result;
     }
 
     /**
@@ -2128,7 +2118,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 +2129,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return result;
+        return (result == null) ? Collections.emptyList() : result;
     }
 
     /**
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/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/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/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/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/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)