Merge "Add some logging about stack visibility in app bubble logs" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0fdd880..cd991c7 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -69,6 +69,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}",
@@ -1138,3 +1139,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/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/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 a3775b0..283e429 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);
@@ -42369,6 +42378,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);
@@ -43536,13 +43547,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 {
@@ -53873,6 +53915,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";
@@ -59602,7 +59645,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/system-current.txt b/core/api/system-current.txt
index 50764b2..66e03db 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";
@@ -4397,6 +4397,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();
@@ -4406,6 +4459,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();
@@ -4415,6 +4494,26 @@
     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 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);
@@ -10069,6 +10168,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);
@@ -10082,6 +10182,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);
@@ -14073,6 +14174,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();
@@ -14343,6 +14511,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
@@ -14825,6 +14995,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();
@@ -15070,6 +15241,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();
@@ -15125,6 +15303,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 850f149..a21d7c4 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>);
+    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);
+  }
+
+  @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 {
@@ -3315,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/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/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/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/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/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 4626679..69f9a7d 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.
@@ -518,6 +521,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())) {
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..2fd322a 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,18 +110,13 @@
 
     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<>());
-    }
-
-    /** 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,
@@ -129,7 +127,13 @@
                 hasPermissionToOverrideDefault, defaultProviderIds);
     }
 
-    /** 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,
@@ -140,24 +144,17 @@
                 /*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<>());
-    }
-
 
     /** Returns whether the calling package has the permission. */
     public boolean hasPermissionToOverrideDefault() {
         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 +182,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.
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/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 241c452..3454c39 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));
         }
     }
 
@@ -400,11 +398,11 @@
         private final String mAltGrText;
 
         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);
         }
 
         public String getBaseText() {
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/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/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 ad0f940..0da19df 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5357,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/provider/Settings.java b/core/java/android/provider/Settings.java
index 76fda06..ef2d5eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18161,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/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/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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2433bd8..738bb1f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5044,10 +5044,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));
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/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..baefe7b 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();
     }
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/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/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/core/CompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
new file mode 100644
index 0000000..ce8ca0d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
@@ -0,0 +1,30 @@
+/*
+ * 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.List;
+
+/**
+ * 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/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
new file mode 100644
index 0000000..abda0c0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Services that are needed to be provided by the platform during encoding.
+ */
+public interface Platform {
+    byte[] imageToByteArray(Object image);
+}
+
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/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
new file mode 100644
index 0000000..c7ec3359
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public interface RemoteComposeOperation extends Operation {
+
+}
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/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/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/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..65c4d9f 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
@@ -2793,6 +2799,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 e006b9d..951625e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4055,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
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/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/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/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/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/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/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 72ca296..621c453 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
@@ -1014,8 +1030,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 +1113,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 +1229,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);
         }
     }
 
@@ -1771,18 +1786,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();
 
@@ -1976,7 +1992,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..c877d4a 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();
@@ -2894,7 +2862,7 @@
         if (!shouldShowFlyout(bubble)) {
             return;
         }
-
+        ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
         mFlyoutDragDeltaX = 0f;
         clearFlyoutOnHide();
         mAfterFlyoutHidden = () -> {
@@ -3036,6 +3004,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 +3138,6 @@
     }
 
     private void updateExpandedBubble() {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "updateExpandedBubble()");
-        }
-
         mExpandedViewContainer.removeAllViews();
         if (mIsExpanded && mExpandedBubble != null
                 && mExpandedBubble.getExpandedView() != null) {
@@ -3318,9 +3285,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/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/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/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 9f94ef9..28cf250 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();
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index f8b640c7..7680136 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.enable_nfc_mainline") @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/nfc/java/android/nfc/INfcVendorNciCallback.aidl b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
new file mode 100644
index 0000000..821dc6f
--- /dev/null
+++ b/nfc/java/android/nfc/INfcVendorNciCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.nfc;
+
+/**
+ * @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 5b917a1..782af5f 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.
@@ -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.
@@ -2962,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_ENABLE_NFC_MAINLINE)
+    @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/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/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/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..30681f3 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
@@ -295,10 +295,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/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/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/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/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index c9b0230..9558bb0 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
@@ -25,20 +25,15 @@
 import androidx.compose.ui.unit.dp
 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.android.credentialmanager.ui.screens.single.SingleAccountScreen
 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
 
 @Composable
 fun SinglePasskeyScreen(
     name: String,
     email: String,
-    onCancelClick: () -> Unit,
-    onOKClick: () -> Unit,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
 ) {
@@ -60,24 +55,6 @@
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
-            DialogButtonsRow(
-                onCancelClick = onCancelClick,
-                onOKClick = onOKClick,
-                modifier = Modifier.padding(top = 10.dp)
-            )
         }
     }
 }
-
-@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/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 9f971ae..5463612 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
@@ -33,15 +33,12 @@
 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.SignInHeader
 import com.android.credentialmanager.ui.model.PasswordUiModel
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
 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
 
 @Composable
 fun SinglePasswordScreen(
@@ -63,8 +60,6 @@
         is SinglePasswordScreenUiState.Loaded -> {
             SinglePasswordScreen(
                 passwordUiModel = state.passwordUiModel,
-                onCancelClick = viewModel::onCancelClick,
-                onOKClick = viewModel::onOKClick,
                 columnState = columnState,
                 modifier = modifier
             )
@@ -102,8 +97,6 @@
 @Composable
 fun SinglePasswordScreen(
     passwordUiModel: PasswordUiModel,
-    onCancelClick: () -> Unit,
-    onOKClick: () -> Unit,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
 ) {
@@ -124,23 +117,6 @@
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
-            DialogButtonsRow(
-                onCancelClick = onCancelClick,
-                onOKClick = onOKClick,
-                modifier = Modifier.padding(top = 10.dp)
-            )
         }
     }
 }
-
-@WearPreview
-@Composable
-fun SinglePasswordScreenPreview() {
-    SinglePasswordScreen(
-        passwordUiModel = PasswordUiModel(email = "beckett_bakery@gmail.com"),
-        onCancelClick = {},
-        onOKClick = {},
-        columnState = belowTimeTextPreview(),
-    )
-}
-
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/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/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/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/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/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/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/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 4cc7332..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
@@ -30,12 +30,13 @@
 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
@@ -129,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,
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/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0e08a19..3fb8254 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,11 +51,13 @@
 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
@@ -63,10 +65,15 @@
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.height
+import com.android.compose.ui.util.lerp
+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/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/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/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/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c82688c..5f932f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -52,7 +52,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 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
@@ -236,9 +236,7 @@
 
         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/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/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/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_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/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..51012a4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -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>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8971859..f0cbe7a 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] -->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6bf4906..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"
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index bbab4de..6314bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -24,4 +24,5 @@
     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
index 8fe9389..6cee28b 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -25,15 +25,21 @@
 import javax.inject.Inject
 import kotlin.math.roundToInt
 
-class CameraProtectionLoader @Inject constructor(private val context: Context) {
+interface CameraProtectionLoader {
+    fun loadCameraProtectionInfoList(): List<CameraProtectionInfo>
+}
 
-    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_frontBuiltInDisplayCutoutProtection,
+                R.string.config_protectedScreenUniqueId,
             )
         if (front != null) {
             list.add(front)
@@ -42,7 +48,8 @@
             loadCameraProtectionInfo(
                 R.string.config_protectedInnerCameraId,
                 R.string.config_protectedInnerPhysicalCameraId,
-                R.string.config_innerBuiltInDisplayCutoutProtection
+                R.string.config_innerBuiltInDisplayCutoutProtection,
+                R.string.config_protectedInnerScreenUniqueId,
             )
         if (inner != null) {
             list.add(inner)
@@ -53,7 +60,8 @@
     private fun loadCameraProtectionInfo(
         cameraIdRes: Int,
         physicalCameraIdRes: Int,
-        pathRes: Int
+        pathRes: Int,
+        displayUniqueIdRes: Int,
     ): CameraProtectionInfo? {
         val logicalCameraId = context.getString(cameraIdRes)
         if (logicalCameraId.isNullOrEmpty()) {
@@ -70,11 +78,13 @@
                 computed.right.roundToInt(),
                 computed.bottom.roundToInt()
             )
+        val displayUniqueId = context.getString(displayUniqueIdRes)
         return CameraProtectionInfo(
             logicalCameraId,
             physicalCameraId,
             protectionPath,
-            protectionBounds
+            protectionBounds,
+            displayUniqueId
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
new file mode 100644
index 0000000..58680a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.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
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CameraProtectionModule {
+
+    @Binds fun cameraProtectionLoaderImpl(impl: CameraProtectionLoaderImpl): CameraProtectionLoader
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
new file mode 100644
index 0000000..fc0b97e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.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
+
+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/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/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/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/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 922baa3..8784723 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,75 @@
 
 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.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,
 ) {
-
-    @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,
+            ) { startedStep, currentState ->
+                // If we're in transition to GONE, special unlock animation params apply.
+                if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
+                    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/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/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/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/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/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/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/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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 06ca3af..e219bcc 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;
@@ -2083,6 +2084,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 +2201,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) {
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/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/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 50b08b8..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
@@ -31,6 +31,7 @@
 
 /** Binds the shared notification container to its view-model. */
 object NotificationStackAppearanceViewBinder {
+    const val SCRIM_CORNER_RADIUS = 32f
 
     @JvmStatic
     fun bind(
@@ -49,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 8b723da..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
@@ -28,7 +28,6 @@
 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
@@ -74,9 +73,6 @@
         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/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..69282ae 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));
         }
     }
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/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/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 64cd526..f776a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -347,8 +347,8 @@
         return CameraAvailabilityListener.build(
                 context,
                 context.mainExecutor,
-                CameraProtectionLoader((context))
-            )
+                CameraProtectionLoaderImpl((context))
+        )
             .also {
                 it.addTransitionCallback(cameraTransitionCallback)
                 it.startListening()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index 238e5e9..a19a0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -27,9 +27,9 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class CameraProtectionLoaderTest : SysuiTestCase() {
+class CameraProtectionLoaderImplTest : SysuiTestCase() {
 
-    private val loader = CameraProtectionLoader(context)
+    private val loader = CameraProtectionLoaderImpl(context)
 
     @Before
     fun setUp() {
@@ -39,19 +39,21 @@
             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 protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+        val protectionList = loadProtectionList()
 
-        assertThat(protectionInfos)
+        assertThat(protectionList)
             .containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
     }
 
@@ -59,18 +61,18 @@
     fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
         overrideResource(R.string.config_protectedCameraId, "")
 
-        val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+        val protectionList = loadProtectionList()
 
-        assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+        assertThat(protectionList).containsExactly(INNER_CAMERA_PROTECTION_INFO)
     }
 
     @Test
     fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
         overrideResource(R.string.config_protectedInnerCameraId, "")
 
-        val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+        val protectionList = loadProtectionList()
 
-        assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+        assertThat(protectionList).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
     }
 
     @Test
@@ -78,13 +80,16 @@
         overrideResource(R.string.config_protectedCameraId, "")
         overrideResource(R.string.config_protectedInnerCameraId, "")
 
-        val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+        val protectionList = loadProtectionList()
 
-        assertThat(protectionInfos).isEmpty()
+        assertThat(protectionList).isEmpty()
     }
 
+    private fun loadProtectionList() =
+        loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
     private fun CameraProtectionInfo.toTestableVersion() =
-        TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+        TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
 
     /**
      * "Testable" version, because the original version contains a Path property, which doesn't
@@ -94,6 +99,7 @@
         val logicalCameraId: String,
         val physicalCameraId: String?,
         val cutoutBounds: Rect,
+        val displayUniqueId: String?,
     )
 
     companion object {
@@ -102,11 +108,13 @@
         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_CAMERA_PROTECTION_BOUNDS,
+                OUTER_SCREEN_UNIQUE_ID,
             )
 
         private const val INNER_CAMERA_LOGICAL_ID = "2"
@@ -114,11 +122,13 @@
         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_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 1f1fa72..c20367e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -177,7 +177,7 @@
             new FakeFacePropertyRepository();
     private List<DecorProvider> mMockCutoutList;
     private final CameraProtectionLoader mCameraProtectionLoader =
-            new CameraProtectionLoader(mContext);
+            new CameraProtectionLoaderImpl(mContext);
 
     @Before
     public void setup() {
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/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/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/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/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 722c11d..668fb64 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,60 @@
 
 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.TransitionInfo
 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 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.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import junit.framework.Assert.fail
 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 +78,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 +92,69 @@
                     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,
-                    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.")
-            }
+            withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+                .also {
+                    assertEquals(KeyguardState.LOCKSCREEN, it.from)
+                    assertEquals(KeyguardState.GONE, it.to)
+                }
         }
 
     @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)
+            verify(transitionRepository, never()).startTransition(any())
         }
-
-    @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..f23dd55 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,176 @@
 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.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()
         }
 }
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..bb61d18 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
@@ -47,16 +42,14 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 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
@@ -89,22 +82,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 +109,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 +137,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(
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/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/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/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 7f4508a..c772ee2 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,9 @@
 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 +83,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;
@@ -213,45 +208,9 @@
                         () -> mFromLockscreenTransitionInteractor,
                         () -> mFromPrimaryBouncerTransitionInteractor);
 
-        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..72c52ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -35,7 +35,6 @@
 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;
@@ -51,13 +50,9 @@
 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 +73,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;
@@ -242,45 +236,9 @@
                         () -> mFromLockscreenTransitionInteractor,
                         () -> mFromPrimaryBouncerTransitionInteractor);
 
-        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..8fd9c80 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.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)
@@ -144,49 +141,8 @@
                 { 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
-            )
+        fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
+        fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
 
         whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
         shadeInteractor =
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/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/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index f4b36659..1d428c8 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;
@@ -108,13 +107,9 @@
 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;
@@ -138,7 +133,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;
@@ -446,45 +440,9 @@
                         () -> mFromPrimaryBouncerTransitionInteractor);
         CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
-        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();
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/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
new file mode 100644
index 0000000..901bdcc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/logging/ScrimLoggerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+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/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/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
new file mode 100644
index 0000000..d0a0e1d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueueKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+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/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/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..a646bc6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.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 android.content.applicationContext
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.keyguardSurfaceBehindInteractor by
+    Kosmos.Fixture {
+        KeyguardSurfaceBehindInteractor(
+            repository = keyguardSurfaceBehindRepository,
+            context = applicationContext,
+            transitionInteractor = keyguardTransitionInteractor,
+            inWindowLauncherUnlockAnimationInteractor = {
+                inWindowLauncherUnlockAnimationInteractor
+            },
+            swipeToDismissInteractor = swipeToDismissInteractor,
+        )
+    }
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/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..0207280
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.windowManagerLockscreenVisibilityInteractor by
+    Kosmos.Fixture {
+        WindowManagerLockscreenVisibilityInteractor(
+            keyguardInteractor = keyguardInteractor,
+            transitionInteractor = keyguardTransitionInteractor,
+            surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
+            fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+            fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+        )
+    }
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/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/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/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/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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 10cd6e5..d110349 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;
             }
 
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/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/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 a61199a..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
@@ -2379,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();
     }
@@ -2782,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);
             }
@@ -2800,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/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/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/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/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/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/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/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/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..b3983e7 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,13 @@
         if (topRunningActivity == null) {
             return false;
         }
-        moveDisplayToTopInternal(topRunningActivity.getDisplayId());
-        handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
-        if (fromWin.isFocused()) {
-            return false;
-        }
-        return true;
+        moveFocusToActivity(topRunningActivity);
+        return !fromWin.isFocused();
+    }
+
+    private 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/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 44cd23d..09c4f7c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,6 +499,10 @@
     }
 
     void applyEnterAnimationLocked() {
+        if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
+            // It's unnecessary to play enter animation below starting window.
+            return;
+        }
         final int transit;
         if (mEnterAnimationPending) {
             mEnterAnimationPending = 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/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index fb0fbe8..50e426c 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -327,7 +327,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/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/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 78ec2ff3..5408e11 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -104,7 +104,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -127,7 +127,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -150,7 +150,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -173,7 +173,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -196,7 +196,7 @@
         int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
         float lastUserSetBrightness = 0.2f;
         boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -221,7 +221,7 @@
         boolean userSetBrightnessChanged = true;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -247,7 +247,7 @@
         float pendingBrightnessAdjustment = 0.1f;
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
                 allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
                 userSetBrightnessChanged);
@@ -411,7 +411,7 @@
     private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
     }
 
     private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
diff --git a/services/tests/mockingservicestests/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/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/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/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/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/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/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/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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 26c3025..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;
         }
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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f82463b..61c7a42 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -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/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)