Merge "Maintain parity with central surfaces impl." into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index ce381b6..e08200b 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -22,7 +22,6 @@
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.PackageOps;
 import android.app.IActivityManager;
-import android.app.UidObserver;
 import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -54,6 +53,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.StatLogger;
+import com.android.modules.expresslog.Counter;
 import com.android.server.AppStateTrackerProto.ExemptedPackage;
 import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages;
 import com.android.server.usage.AppStandbyInternal;
@@ -79,6 +79,9 @@
 public class AppStateTrackerImpl implements AppStateTracker {
     private static final boolean DEBUG = false;
 
+    private static final String APP_RESTRICTION_COUNTER_METRIC_ID =
+            "battery.value_app_background_restricted";
+
     private final Object mLock = new Object();
     private final Context mContext;
 
@@ -748,6 +751,9 @@
             } catch (RemoteException e) {
                 // Shouldn't happen
             }
+            if (restricted) {
+                Counter.logIncrementWithUid(APP_RESTRICTION_COUNTER_METRIC_ID, uid);
+            }
             synchronized (mLock) {
                 if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
                     mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
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 b9b825c..a0634f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -62,6 +62,7 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.StatLogger;
+import com.android.modules.expresslog.Histogram;
 import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
@@ -471,6 +472,13 @@
     private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
             PackageStats::resetStagedCount;
 
+    private static final Histogram sConcurrencyHistogramLogger = new Histogram(
+            "job_scheduler.value_hist_job_concurrency",
+            // Create a histogram that expects values in the range [0, 99].
+            // Include more buckets than MAX_CONCURRENCY_LIMIT to account for/identify the cases
+            // where we may create additional slots for TOP-started EJs and UIJs
+            new Histogram.UniformOptions(100, 0, 99));
+
     private final StatLogger mStatLogger = new StatLogger(new String[]{
             "assignJobsToContexts",
             "refreshSystemState",
@@ -1433,6 +1441,7 @@
         mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
                 // TODO: log per type instead of only TOP
                 mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
+        sConcurrencyHistogramLogger.logSample(mActiveServices.size());
     }
 
     @GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index d94674b..8a5d094 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -33,6 +33,7 @@
 import android.util.SparseSetArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.modules.expresslog.Counter;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.notification.NotificationManagerInternal;
@@ -114,11 +115,39 @@
             @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
         validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy);
         final JobStatus jobStatus = hostingContext.getRunningJobLocked();
+        if (jobStatus == null) {
+            Slog.wtfStack(TAG, "enqueueNotification called with no running job");
+            return;
+        }
         final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext);
-        if (oldDetails != null && oldDetails.notificationId != notificationId) {
-            // App is switching notification IDs. Remove association with the old one.
-            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
-                    jobStatus);
+        if (oldDetails == null) {
+            if (jobStatus.startedAsUserInitiatedJob) {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_initial_setNotification_call_required",
+                        jobStatus.getUid());
+            } else {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_initial_setNotification_call_optional",
+                        jobStatus.getUid());
+            }
+        } else {
+            if (jobStatus.startedAsUserInitiatedJob) {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_required",
+                        jobStatus.getUid());
+            } else {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_optional",
+                        jobStatus.getUid());
+            }
+            if (oldDetails.notificationId != notificationId) {
+                // App is switching notification IDs. Remove association with the old one.
+                removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
+                        jobStatus);
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_setNotification_changed_notification_ids",
+                        jobStatus.getUid());
+            }
         }
         final int userId = UserHandle.getUserId(callingUid);
         if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
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 8a4b464..72e6645 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -106,6 +106,8 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
+import com.android.modules.expresslog.Histogram;
 import com.android.server.AppSchedulingModuleThread;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
@@ -378,6 +380,28 @@
     private final long[] mLastCancelledJobTimeElapsed =
             new long[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
 
+    private static final Histogram sEnqueuedJwiHighWaterMarkLogger = new Histogram(
+            "job_scheduler.value_hist_w_uid_enqueued_work_items_high_water_mark",
+            new Histogram.ScaledRangeOptions(25, 0, 5, 1.4f));
+    private static final Histogram sInitialJobEstimatedNetworkDownloadKBLogger = new Histogram(
+            "job_scheduler.value_hist_initial_job_estimated_network_download_kilobytes",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sInitialJwiEstimatedNetworkDownloadKBLogger = new Histogram(
+            "job_scheduler.value_hist_initial_jwi_estimated_network_download_kilobytes",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sInitialJobEstimatedNetworkUploadKBLogger = new Histogram(
+            "job_scheduler.value_hist_initial_job_estimated_network_upload_kilobytes",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sInitialJwiEstimatedNetworkUploadKBLogger = new Histogram(
+            "job_scheduler.value_hist_initial_jwi_estimated_network_upload_kilobytes",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sJobMinimumChunkKBLogger = new Histogram(
+            "job_scheduler.value_hist_w_uid_job_minimum_chunk_kilobytes",
+            new Histogram.ScaledRangeOptions(25, 0, 5 /* 5 KB */, 1.76f));
+    private static final Histogram sJwiMinimumChunkKBLogger = new Histogram(
+            "job_scheduler.value_hist_w_uid_jwi_minimum_chunk_kilobytes",
+            new Histogram.ScaledRangeOptions(25, 0, 5 /* 5 KB */, 1.76f));
+
     /**
      * A mapping of which uids are currently in the foreground to their effective bias.
      */
@@ -1422,6 +1446,32 @@
             return JobScheduler.RESULT_FAILURE;
         }
 
+        if (job.getRequiredNetwork() != null) {
+            sInitialJobEstimatedNetworkDownloadKBLogger.logSample(
+                    safelyScaleBytesToKBForHistogram(
+                            job.getEstimatedNetworkDownloadBytes()));
+            sInitialJobEstimatedNetworkUploadKBLogger.logSample(
+                    safelyScaleBytesToKBForHistogram(job.getEstimatedNetworkUploadBytes()));
+            sJobMinimumChunkKBLogger.logSampleWithUid(uId,
+                    safelyScaleBytesToKBForHistogram(job.getMinimumNetworkChunkBytes()));
+            if (work != null) {
+                sInitialJwiEstimatedNetworkDownloadKBLogger.logSample(
+                        safelyScaleBytesToKBForHistogram(
+                                work.getEstimatedNetworkDownloadBytes()));
+                sInitialJwiEstimatedNetworkUploadKBLogger.logSample(
+                        safelyScaleBytesToKBForHistogram(
+                                work.getEstimatedNetworkUploadBytes()));
+                sJwiMinimumChunkKBLogger.logSampleWithUid(uId,
+                        safelyScaleBytesToKBForHistogram(
+                                work.getMinimumNetworkChunkBytes()));
+            }
+        }
+
+        if (work != null) {
+            Counter.logIncrementWithUid(
+                    "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", uId);
+        }
+
         synchronized (mLock) {
             final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
 
@@ -1451,6 +1501,7 @@
 
                     toCancel.enqueueWorkLocked(work);
                     mJobs.touchJob(toCancel);
+                    sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount());
 
                     // If any of work item is enqueued when the source is in the foreground,
                     // exempt the entire job.
@@ -1483,6 +1534,8 @@
             if (packageName == null) {
                 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
                     Slog.w(TAG, "Too many jobs for uid " + uId);
+                    Counter.logIncrementWithUid(
+                            "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", uId);
                     throw new IllegalStateException("Apps may not schedule more than "
                             + MAX_JOBS_PER_APP + " distinct jobs");
                 }
@@ -1522,6 +1575,7 @@
             if (work != null) {
                 // If work has been supplied, enqueue it into the new job.
                 jobStatus.enqueueWorkLocked(work);
+                sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, jobStatus.getWorkCount());
             }
 
             FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
@@ -3834,6 +3888,18 @@
         return bucket;
     }
 
+    static int safelyScaleBytesToKBForHistogram(long bytes) {
+        long kilobytes = bytes / 1000;
+        // Anything over Integer.MAX_VALUE or under Integer.MIN_VALUE isn't expected and will
+        // be put into the overflow buckets.
+        if (kilobytes > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        } else if (kilobytes < Integer.MIN_VALUE) {
+            return Integer.MIN_VALUE;
+        }
+        return (int) kilobytes;
+    }
+
     private class CloudProviderChangeListener implements
             StorageManagerInternal.CloudProviderChangeListener {
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 3fbb337..2944095e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -20,6 +20,7 @@
 
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.safelyScaleBytesToKBForHistogram;
 
 import android.Manifest;
 import android.annotation.BytesLong;
@@ -66,6 +67,8 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
+import com.android.modules.expresslog.Histogram;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
@@ -111,6 +114,22 @@
     private static final long NOTIFICATION_TIMEOUT_MILLIS = 10_000L * Build.HW_TIMEOUT_MULTIPLIER;
     private static final long EXECUTION_DURATION_STAMP_PERIOD_MILLIS = 5 * 60_000L;
 
+    private static final Histogram sEnqueuedJwiAtJobStart = new Histogram(
+            "job_scheduler.value_hist_w_uid_enqueued_work_items_at_job_start",
+            new Histogram.ScaledRangeOptions(20, 1, 3, 1.4f));
+    private static final Histogram sTransferredNetworkDownloadKBHighWaterMarkLogger = new Histogram(
+            "job_scheduler.value_hist_transferred_network_download_kilobytes_high_water_mark",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sTransferredNetworkUploadKBHighWaterMarkLogger = new Histogram(
+            "job_scheduler.value_hist_transferred_network_upload_kilobytes_high_water_mark",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sUpdatedEstimatedNetworkDownloadKBLogger = new Histogram(
+            "job_scheduler.value_hist_updated_estimated_network_download_kilobytes",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+    private static final Histogram sUpdatedEstimatedNetworkUploadKBLogger = new Histogram(
+            "job_scheduler.value_hist_updated_estimated_network_upload_kilobytes",
+            new Histogram.ScaledRangeOptions(50, 0, 32 /* 32 KB */, 1.31f));
+
     private static final String[] VERB_STRINGS = {
             "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
     };
@@ -480,6 +499,7 @@
                     job.getEstimatedNetworkUploadBytes(),
                     job.getWorkCount(),
                     ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())));
+            sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
                 final String componentPackage = job.getServiceComponent().getPackageName();
@@ -816,6 +836,41 @@
             if (!verifyCallerLocked(cb)) {
                 return;
             }
+            Counter.logIncrementWithUid(
+                    "job_scheduler.value_cntr_w_uid_estimated_network_bytes_updated",
+                    mRunningJob.getUid());
+            sUpdatedEstimatedNetworkDownloadKBLogger.logSample(
+                    safelyScaleBytesToKBForHistogram(downloadBytes));
+            sUpdatedEstimatedNetworkUploadKBLogger.logSample(
+                    safelyScaleBytesToKBForHistogram(uploadBytes));
+            if (mEstimatedDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN
+                    && downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                if (mEstimatedDownloadBytes < downloadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler."
+                                    + "value_cntr_w_uid_estimated_network_download_bytes_increased",
+                            mRunningJob.getUid());
+                } else if (mEstimatedDownloadBytes > downloadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler."
+                                    + "value_cntr_w_uid_estimated_network_download_bytes_decreased",
+                            mRunningJob.getUid());
+                }
+            }
+            if (mEstimatedUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN
+                    && uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                if (mEstimatedUploadBytes < uploadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler"
+                                    + ".value_cntr_w_uid_estimated_network_upload_bytes_increased",
+                            mRunningJob.getUid());
+                } else if (mEstimatedUploadBytes > uploadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler"
+                                    + ".value_cntr_w_uid_estimated_network_upload_bytes_decreased",
+                            mRunningJob.getUid());
+                }
+            }
             mEstimatedDownloadBytes = downloadBytes;
             mEstimatedUploadBytes = uploadBytes;
         }
@@ -828,6 +883,41 @@
             if (!verifyCallerLocked(cb)) {
                 return;
             }
+            Counter.logIncrementWithUid(
+                    "job_scheduler.value_cntr_w_uid_transferred_network_bytes_updated",
+                    mRunningJob.getUid());
+            sTransferredNetworkDownloadKBHighWaterMarkLogger.logSample(
+                    safelyScaleBytesToKBForHistogram(downloadBytes));
+            sTransferredNetworkUploadKBHighWaterMarkLogger.logSample(
+                    safelyScaleBytesToKBForHistogram(uploadBytes));
+            if (mTransferredDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN
+                    && downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                if (mTransferredDownloadBytes < downloadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler."
+                                    + "value_cntr_w_uid_transferred_network_download_bytes_increased",
+                            mRunningJob.getUid());
+                } else if (mTransferredDownloadBytes > downloadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler."
+                                    + "value_cntr_w_uid_transferred_network_download_bytes_decreased",
+                            mRunningJob.getUid());
+                }
+            }
+            if (mTransferredUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN
+                    && uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                if (mTransferredUploadBytes < uploadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler."
+                                    + "value_cntr_w_uid_transferred_network_upload_bytes_increased",
+                            mRunningJob.getUid());
+                } else if (mTransferredUploadBytes > uploadBytes) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler."
+                                    + "value_cntr_w_uid_transferred_network_upload_bytes_decreased",
+                            mRunningJob.getUid());
+                }
+            }
             mTransferredDownloadBytes = downloadBytes;
             mTransferredUploadBytes = uploadBytes;
         }
@@ -898,6 +988,11 @@
                 // Use that as the stop reason for logging/debugging purposes.
                 mParams.setStopReason(
                         mDeathMarkStopReason, mDeathMarkInternalStopReason, mDeathMarkDebugReason);
+            } else if (mRunningJob != null) {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_unexpected_service_disconnects",
+                        // Use the calling UID since that's the one this context was connected to.
+                        mRunningJob.getUid());
             }
             closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
         }
@@ -1224,6 +1319,8 @@
         switch (mVerb) {
             case VERB_BINDING:
                 onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
+                        /* texCounterMetricId */
+                        "job_scheduler.value_cntr_w_uid_slow_app_response_binding",
                         /* debugReason */ "timed out while binding",
                         /* anrMessage */ "Timed out while trying to bind",
                         CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1234,6 +1331,8 @@
                 // know what happened so let's log it and notify the JobScheduler
                 // FINISHED/NO-RETRY.
                 onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
+                        /* texCounterMetricId */
+                        "job_scheduler.value_cntr_w_uid_slow_app_response_onStartJob",
                         /* debugReason */ "timed out while starting",
                         /* anrMessage */ "No response to onStartJob",
                         CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1244,6 +1343,8 @@
                 // Don't update the stop reasons since we were already stopping the job for some
                 // other reason.
                 onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ false,
+                        /* texCounterMetricId */
+                        "job_scheduler.value_cntr_w_uid_slow_app_response_onStopJob",
                         /* debugReason */ "timed out while stopping",
                         /* anrMessage */ "No response to onStopJob",
                         CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1299,6 +1400,8 @@
                     }
                 } else if (mAwaitingNotification) {
                     onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ true,
+                            /* texCounterMetricId */
+                            "job_scheduler.value_cntr_w_uid_slow_app_response_setNotification",
                             /* debugReason */ "timed out while stopping",
                             /* anrMessage */ "required notification not provided",
                             /* triggerAnr */ true);
@@ -1348,8 +1451,11 @@
 
     @GuardedBy("mLock")
     private void onSlowAppResponseLocked(boolean reschedule, boolean updateStopReasons,
+            @NonNull String texCounterMetricId,
             @NonNull String debugReason, @NonNull String anrMessage, boolean triggerAnr) {
         Slog.w(TAG, anrMessage + " for " + getRunningJobNameLocked());
+        // Use the calling UID since that's the one this context was connected to.
+        Counter.logIncrementWithUid(texCounterMetricId, mRunningJob.getUid());
         if (updateStopReasons) {
             mParams.setStopReason(
                     JobParameters.STOP_REASON_UNDEFINED,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index c540517..0a7bffc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -49,8 +49,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
+import com.android.modules.expresslog.Histogram;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.IoThread;
 import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
 import com.android.server.job.controllers.JobStatus;
@@ -94,6 +96,7 @@
 
     /** Threshold to adjust how often we want to write to the db. */
     private static final long JOB_PERSIST_DELAY = 2000L;
+    private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L;
     @VisibleForTesting
     static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
     private static final int ALL_UIDS = -1;
@@ -131,6 +134,30 @@
 
     private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
 
+    /**
+     * Separately updated value of the JobSet size to avoid recalculating it frequently for logging
+     * purposes. Continue to use {@link JobSet#size()} for the up-to-date and accurate value.
+     */
+    private int mCurrentJobSetSize = 0;
+    private int mScheduledJob30MinHighWaterMark = 0;
+    private static final Histogram sScheduledJob30MinHighWaterMarkLogger = new Histogram(
+            "job_scheduler.value_hist_scheduled_job_30_min_high_water_mark",
+            new Histogram.ScaledRangeOptions(15, 1, 99, 1.5f));
+    private final Runnable mScheduledJobHighWaterMarkLoggingRunnable = new Runnable() {
+        @Override
+        public void run() {
+            AppSchedulingModuleThread.getHandler().removeCallbacks(this);
+            synchronized (mLock) {
+                sScheduledJob30MinHighWaterMarkLogger.logSample(mScheduledJob30MinHighWaterMark);
+                mScheduledJob30MinHighWaterMark = mJobSet.size();
+            }
+            // The count doesn't need to be logged at exact times. Logging based on system uptime
+            // should be fine.
+            AppSchedulingModuleThread.getHandler()
+                    .postDelayed(this, SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS);
+        }
+    };
+
     /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
     static JobStore get(JobSchedulerService jobManagerService) {
         synchronized (sSingletonLock) {
@@ -183,6 +210,9 @@
         mXmlTimestamp = mJobsFile.exists()
                 ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified();
         mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
+
+        AppSchedulingModuleThread.getHandler().postDelayed(
+                mScheduledJobHighWaterMarkLoggingRunnable, SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS);
     }
 
     private void init() {
@@ -252,7 +282,10 @@
      * @param jobStatus Job to add.
      */
     public void add(JobStatus jobStatus) {
-        mJobSet.add(jobStatus);
+        if (mJobSet.add(jobStatus)) {
+            mCurrentJobSetSize++;
+            maybeUpdateHighWaterMark();
+        }
         if (jobStatus.isPersisted()) {
             mPendingJobWriteUids.put(jobStatus.getUid(), true);
             maybeWriteStatusToDiskAsync();
@@ -267,7 +300,10 @@
      */
     @VisibleForTesting
     public void addForTesting(JobStatus jobStatus) {
-        mJobSet.add(jobStatus);
+        if (mJobSet.add(jobStatus)) {
+            mCurrentJobSetSize++;
+            maybeUpdateHighWaterMark();
+        }
         if (jobStatus.isPersisted()) {
             mPendingJobWriteUids.put(jobStatus.getUid(), true);
         }
@@ -303,6 +339,7 @@
             }
             return false;
         }
+        mCurrentJobSetSize--;
         if (removeFromPersisted && jobStatus.isPersisted()) {
             mPendingJobWriteUids.put(jobStatus.getUid(), true);
             maybeWriteStatusToDiskAsync();
@@ -315,7 +352,9 @@
      */
     @VisibleForTesting
     public void removeForTesting(JobStatus jobStatus) {
-        mJobSet.remove(jobStatus);
+        if (mJobSet.remove(jobStatus)) {
+            mCurrentJobSetSize--;
+        }
         if (jobStatus.isPersisted()) {
             mPendingJobWriteUids.put(jobStatus.getUid(), true);
         }
@@ -327,6 +366,7 @@
      */
     public void removeJobsOfUnlistedUsers(int[] keepUserIds) {
         mJobSet.removeJobsOfUnlistedUsers(keepUserIds);
+        mCurrentJobSetSize = mJobSet.size();
     }
 
     /** Note a change in the specified JobStatus that necessitates writing job state to disk. */
@@ -342,6 +382,7 @@
     public void clear() {
         mJobSet.clear();
         mPendingJobWriteUids.put(ALL_UIDS, true);
+        mCurrentJobSetSize = 0;
         maybeWriteStatusToDiskAsync();
     }
 
@@ -352,6 +393,7 @@
     public void clearForTesting() {
         mJobSet.clear();
         mPendingJobWriteUids.put(ALL_UIDS, true);
+        mCurrentJobSetSize = 0;
     }
 
     void setUseSplitFiles(boolean useSplitFiles) {
@@ -442,6 +484,12 @@
         mJobSet.forEachJobForSourceUid(sourceUid, functor);
     }
 
+    private void maybeUpdateHighWaterMark() {
+        if (mScheduledJob30MinHighWaterMark < mCurrentJobSetSize) {
+            mScheduledJob30MinHighWaterMark = mCurrentJobSetSize;
+        }
+    }
+
     /** Version of the db schema. */
     private static final int JOBS_FILE_VERSION = 1;
     /**
@@ -1125,6 +1173,12 @@
             if (needFileMigration) {
                 migrateJobFilesAsync();
             }
+
+            // Log the count immediately after loading from boot.
+            mCurrentJobSetSize = numJobs;
+            mScheduledJob30MinHighWaterMark = mCurrentJobSetSize;
+            mScheduledJobHighWaterMarkLoggingRunnable.run();
+
             if (mCompletionLatch != null) {
                 mCompletionLatch.countDown();
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index ba62e96..c272af0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -90,6 +90,7 @@
             final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
                 // We're intentionally excluding jobs whose deadlines have passed
+                // from the job_scheduler.value_job_scheduler_job_deadline_expired_counter count
                 // (mostly like deadlines of 0) when the job was scheduled.
                 return;
             } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9346223..e42e526 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2367,6 +2367,7 @@
     method public static boolean isApp(int);
     field public static final int MIN_SECONDARY_USER_ID = 10; // 0xa
     field public static final int USER_ALL = -1; // 0xffffffff
+    field public static final int USER_CURRENT = -2; // 0xfffffffe
     field public static final int USER_NULL = -10000; // 0xffffd8f0
     field public static final int USER_SYSTEM = 0; // 0x0
   }
@@ -2375,10 +2376,12 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getAliveUsers();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
     method public int getMainDisplayIdAssignedToUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers();
     method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9e59ee4..0e5cbe2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -60,13 +60,16 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.Pools;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -179,6 +182,8 @@
  */
 @SystemService(Context.APP_OPS_SERVICE)
 public class AppOpsManager {
+    private static final String LOG_TAG = "AppOpsManager";
+
     /**
      * This is a subtle behavior change to {@link #startWatchingMode}.
      *
@@ -7517,6 +7522,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setUidMode(int code, int uid, @Mode int mode) {
+        logAnySeriousModeChanges(code, uid, null, mode);
         try {
             mService.setUidMode(code, uid, mode);
         } catch (RemoteException e) {
@@ -7537,6 +7543,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
+        logAnySeriousModeChanges(strOpToOp(appOp), uid, null, mode);
         try {
             mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
         } catch (RemoteException e) {
@@ -7572,11 +7579,32 @@
         }
     }
 
+    private void logAnySeriousModeChanges(int code, int uid, String packageName, @Mode int mode) {
+        // TODO (b/280869337): Remove this once we have the required data.
+        if (code != OP_RUN_ANY_IN_BACKGROUND || mode == MODE_ALLOWED) {
+            return;
+        }
+        final StringBuilder log = new StringBuilder("Attempt to change RUN_ANY_IN_BACKGROUND to ")
+                .append(modeToName(mode))
+                .append(" for uid: ")
+                .append(UserHandle.formatUid(uid))
+                .append(" package: ")
+                .append(packageName)
+                .append(" by: ")
+                .append(mContext.getOpPackageName());
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            Slog.wtfStack(LOG_TAG, log.toString());
+        } else {
+            Log.w(LOG_TAG, log.toString());
+        }
+    }
+
     /** @hide */
     @UnsupportedAppUsage
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setMode(int code, int uid, String packageName, @Mode int mode) {
+        logAnySeriousModeChanges(code, uid, packageName, mode);
         try {
             mService.setMode(code, uid, packageName, mode);
         } catch (RemoteException e) {
@@ -7599,6 +7627,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setMode(@NonNull String op, int uid, @Nullable String packageName,
             @Mode int mode) {
+        logAnySeriousModeChanges(strOpToOp(op), uid, packageName, mode);
         try {
             mService.setMode(strOpToOp(op), uid, packageName, mode);
         } catch (RemoteException e) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 99ef315..e9fbf6b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -102,6 +102,43 @@
     void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
             String callingPackage);
     void unregisterUidObserver(in IUidObserver observer);
+
+    /**
+     * Registers a UidObserver with a uid filter.
+     *
+     * @param observer The UidObserver implementation to register.
+     * @param which    A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*.
+     * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this
+     *                 threshold in either direction, onUidStateChanged will be called.
+     * @param callingPackage The name of the calling package.
+     * @param uids     A list of uids to watch. If all uids are to be watched, use
+     *                 registerUidObserver instead.
+     * @throws RemoteException
+     * @return Returns A binder token identifying the UidObserver registration.
+     */
+    IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
+            String callingPackage, in int[] uids);
+
+    /**
+     * Adds a uid to the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to watch.
+     * @throws RemoteException
+     */
+    void addUidToObserver(in IBinder observerToken, String callingPackage, int uid);
+
+    /**
+     * Removes a uid from the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to stop watching.
+     * @throws RemoteException
+     */
+    void removeUidFromObserver(in IBinder observerToken, String callingPackage, int uid);
+
     boolean isUidActive(int uid, String callingPackage);
     @JavaPassthrough(annotation=
             "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)")
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 2b15589..4d308d9 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -259,6 +259,14 @@
     boolean lockScreenWallpaperExists();
 
     /**
+     * Return true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK,
+     * always return false if the lock screen doesn't run its own wallpaper engine.
+     *
+     * @hide
+     */
+    boolean isStaticWallpaper(int which);
+
+    /**
      * Temporary method for project b/197814683.
      * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image.
      * @hide
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f673304..c0106ab 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -231,6 +231,16 @@
     public static final String COMMAND_WAKING_UP = "android.wallpaper.wakingup";
 
     /**
+     * Command for {@link #sendWallpaperCommand}: reported by System UI when the device keyguard
+     * starts going away.
+     * This command is triggered by {@link android.app.IActivityTaskManager#keyguardGoingAway(int)}.
+     *
+     * @hide
+     */
+    public static final String COMMAND_KEYGUARD_GOING_AWAY =
+            "android.wallpaper.keyguardgoingaway";
+
+    /**
      * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is going to
      * sleep. The x and y arguments are a location (possibly very roughly) corresponding to the
      * action that caused the device to go to sleep. For example, if the power button was pressed,
@@ -648,15 +658,8 @@
                     return currentWallpaper;
                 }
             }
-            if (returnDefault) {
-                Bitmap defaultWallpaper = mDefaultWallpaper;
-                if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
-                    defaultWallpaper = getDefaultWallpaper(context, which);
-                    synchronized (this) {
-                        mDefaultWallpaper = defaultWallpaper;
-                    }
-                }
-                return defaultWallpaper;
+            if (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK))) {
+                return getDefaultWallpaper(context, which);
             }
             return null;
         }
@@ -695,7 +698,7 @@
             }
             // If user wallpaper is unavailable, may be the default one instead.
             if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
-                    && returnDefault) {
+                    && (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK)))) {
                 InputStream is = openDefaultWallpaper(context, which);
                 if (is != null) {
                     try {
@@ -759,18 +762,39 @@
         }
 
         private Bitmap getDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
-            InputStream is = openDefaultWallpaper(context, which);
-            if (is != null) {
-                try {
-                    BitmapFactory.Options options = new BitmapFactory.Options();
-                    return BitmapFactory.decodeStream(is, null, options);
-                } catch (OutOfMemoryError e) {
+            Bitmap defaultWallpaper = mDefaultWallpaper;
+            if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
+                defaultWallpaper = null;
+                try (InputStream is = openDefaultWallpaper(context, which)) {
+                    if (is != null) {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        defaultWallpaper = BitmapFactory.decodeStream(is, null, options);
+                    }
+                } catch (OutOfMemoryError | IOException e) {
                     Log.w(TAG, "Can't decode stream", e);
-                } finally {
-                    IoUtils.closeQuietly(is);
                 }
             }
-            return null;
+            synchronized (this) {
+                mDefaultWallpaper = defaultWallpaper;
+            }
+            return defaultWallpaper;
+        }
+
+        /**
+         * Return true if there is a static wallpaper on the specified screen.
+         * With {@code which=}{@link #FLAG_LOCK}, always return false if the lockscreen doesn't run
+         * its own wallpaper engine.
+         */
+        private boolean isStaticWallpaper(@SetWallpaperFlags int which) {
+            if (mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+                throw new RuntimeException(new DeadSystemException());
+            }
+            try {
+                return mService.isStaticWallpaper(which);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -872,21 +896,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the current system wallpaper; if
-     * no wallpaper is set, the system built-in static wallpaper is returned.
-     * This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
      * <p>
-     * This method can return null if there is no system wallpaper available, if
-     * wallpapers are not supported in the current user, or if the calling app is not
-     * permitted to access the system wallpaper.
+     * Equivalent to {@link #getDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}.
+     * </p>
      *
-     * @return Returns a Drawable object that will draw the system wallpaper,
-     *     or {@code null} if no system wallpaper exists or if the calling application
-     *     is not able to access the wallpaper.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getDrawable(int)
      *
      * @throws SecurityException as described in the note
      */
@@ -909,23 +926,29 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the requested wallpaper; if
-     * no wallpaper is set, the requested built-in static wallpaper is returned.
-     * This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
      * <p>
-     * This method can return null if the requested wallpaper is not available, if
-     * wallpapers are not supported in the current user, or if the calling app is not
-     * permitted to access the requested wallpaper.
+     * Retrieve the requested wallpaper for the specified wallpaper type if the wallpaper is not
+     * a live wallpaper. This method should not be used to display the user wallpaper on an app:
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER} should be used instead.
+     * </p>
+     * <p>
+     * When called with {@code which=}{@link #FLAG_SYSTEM},
+     * if there is a live wallpaper on home screen, the built-in default wallpaper is returned.
+     * </p>
+     * <p>
+     * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper
+     * on lock screen, or if the lock screen and home screen share the same wallpaper engine,
+     * {@code null} is returned.
+     * </p>
+     * <p>
+     * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper
+     * on a specified screen type.
+     * </p>
      *
-     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns a Drawable object that will draw the requested wallpaper,
-     *     or {@code null} if the requested wallpaper does not exist or if the calling application
-     *     is not able to access the wallpaper.
+     * @return A Drawable object for the requested wallpaper.
      *
      * @throws SecurityException as described in the note
      */
@@ -933,7 +956,8 @@
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
+        boolean returnDefault = which != FLAG_LOCK;
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -1165,15 +1189,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the current system wallpaper; if there is no wallpaper set,
-     * a null pointer is returned. This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
+     * <p>
+     * Equivalent to {@link #getDrawable()}.
+     * </p>
      *
-     * @return Returns a Drawable object that will draw the wallpaper or a
-     * null pointer if wallpaper is unset.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getDrawable()
      *
      * @throws SecurityException as described in the note
      */
@@ -1196,31 +1219,23 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the requested wallpaper; if there is no wallpaper set,
-     * a null pointer is returned. This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
+     * <p>
+     * Equivalent to {@link #getDrawable(int)}.
+     * </p>
      *
-     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns a Drawable object that will draw the wallpaper or a null pointer if
-     * wallpaper is unset.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getDrawable(int)
      *
      * @throws SecurityException as described in the note
      */
     @Nullable
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekDrawable(@SetWallpaperFlags int which) {
-        final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
-        if (bm != null) {
-            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
-            dr.setDither(false);
-            return dr;
-        }
-        return null;
+        return getDrawable(which);
     }
 
     /**
@@ -1236,19 +1251,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Like {@link #getDrawable()}, but the returned Drawable has a number
-     * of limitations to reduce its overhead as much as possible. It will
-     * never scale the wallpaper (only centering it if the requested bounds
-     * do match the bitmap bounds, which should not be typical), doesn't
-     * allow setting an alpha, color filter, or other attributes, etc.  The
-     * bounds of the returned drawable will be initialized to the same bounds
-     * as the wallpaper, so normally you will not need to touch it.  The
-     * drawable also assumes that it will be used in a context running in
-     * the same density as the screen (not in density compatibility mode).
+     * <p>
+     * Equivalent to {@link #getFastDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}.
+     * </p>
      *
-     * @return Returns a Drawable object that will draw the wallpaper.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getFastDrawable(int)
      *
      * @throws SecurityException as described in the note
      */
@@ -1285,7 +1295,8 @@
      *
      * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns a Drawable object that will draw the wallpaper.
+     * @return An optimized Drawable object for the requested wallpaper, or {@code null}
+     *     in some cases as specified in {@link #getDrawable(int)}.
      *
      * @throws SecurityException as described in the note
      */
@@ -1293,7 +1304,8 @@
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getFastDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
+        boolean returnDefault = which != FLAG_LOCK;
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -1313,13 +1325,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
-     * a null pointer is returned.
+     * <p>
+     * Equivalent to {@link #getFastDrawable()}.
+     * </p>
      *
-     * @return Returns an optimized Drawable object that will draw the
-     * wallpaper or a null pointer if these is none.
+     * @return An optimized Drawable object for the requested wallpaper.
+     *
+     * @see #getFastDrawable()
      *
      * @throws SecurityException as described in the note
      */
@@ -1342,31 +1355,29 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
-     * a null pointer is returned.
+     * <p>
+     * Equivalent to {@link #getFastDrawable(int)}.
+     * </p>
      *
      * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns an optimized Drawable object that will draw the
-     * wallpaper or a null pointer if these is none.
+     * @return An optimized Drawable object for the requested wallpaper.
      *
      * @throws SecurityException as described in the note
      */
     @Nullable
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekFastDrawable(@SetWallpaperFlags int which) {
-        final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
-        if (bm != null) {
-            return new FastBitmapDrawable(bm);
-        }
-        return null;
+        return getFastDrawable(which);
     }
 
     /**
-     * Whether the wallpaper supports Wide Color Gamut or not.
+     * Whether the wallpaper supports Wide Color Gamut or not. This is only meant to be used by
+     * ImageWallpaper, and will always return false if the wallpaper for the specified screen
+     * is not an ImageWallpaper. This will also return false when called with {@link #FLAG_LOCK} if
+     * the lock and home screen share the same wallpaper engine.
+     *
      * @param which The wallpaper whose image file is to be retrieved. Must be a single
      *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
      * @return true when supported.
@@ -1412,7 +1423,7 @@
     }
 
     /**
-     * Like {@link #getDrawable()} but returns a Bitmap.
+     * Like {@link #getDrawable(int)} but returns a Bitmap.
      *
      * @param hardware Asks for a hardware backed bitmap.
      * @param which Specifies home or lock screen
@@ -1435,7 +1446,7 @@
     }
 
     /**
-     * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
+     * Like {@link #getDrawable(int)} but returns a Bitmap for the provided user.
      *
      * @param which Specifies home or lock screen
      * @hide
@@ -1443,12 +1454,29 @@
     @TestApi
     @Nullable
     public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+        boolean returnDefault = which != FLAG_LOCK;
+        return getBitmapAsUser(userId, hardware, which, returnDefault);
+    }
+
+    /**
+     * Overload of {@link #getBitmapAsUser(int, boolean, int)} with a returnDefault argument.
+     *
+     * @param returnDefault If true, return the default static wallpaper if no custom static
+     *                      wallpaper is set on the specified screen.
+     *                      If false, return {@code null} in that case.
+     * @hide
+     */
+    @Nullable
+    public Bitmap getBitmapAsUser(int userId, boolean hardware,
+            @SetWallpaperFlags int which, boolean returnDefault) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
+        return sGlobals.peekWallpaperBitmap(mContext, returnDefault,
+                which, userId, hardware, cmProxy);
     }
 
     /**
      * Peek the dimensions of system wallpaper of the user without decoding it.
+     * Equivalent to {@link #peekBitmapDimensions(int)} with {@code which=}{@link #FLAG_SYSTEM}.
      *
      * @return the dimensions of system wallpaper
      * @hide
@@ -1462,16 +1490,45 @@
     /**
      * Peek the dimensions of given wallpaper of the user without decoding it.
      *
-     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
-     *     {@link #FLAG_LOCK}.
-     * @return the dimensions of system wallpaper
+     * <p>
+     * When called with {@code which=}{@link #FLAG_SYSTEM}, if there is a live wallpaper on
+     * home screen, the built-in default wallpaper dimensions are returned.
+     * </p>
+     * <p>
+     * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper
+     * on lock screen, or if the lock screen and home screen share the same wallpaper engine,
+     * {@code null} is returned.
+     * </p>
+     * <p>
+     * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper
+     * on a specified screen type.
+     * </p>
+     *
+     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @return the dimensions of specified wallpaper
      * @hide
      */
     @TestApi
     @Nullable
     public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+        boolean returnDefault = which != FLAG_LOCK;
+        return peekBitmapDimensions(which, returnDefault);
+    }
+
+    /**
+     * Overload of {@link #peekBitmapDimensions(int)} with a returnDefault argument.
+     *
+     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @param returnDefault If true, always return the default static wallpaper dimensions
+     *                      if no custom static wallpaper is set on the specified screen.
+     *                      If false, always return {@code null} in that case.
+     * @return the dimensions of specified wallpaper
+     * @hide
+     */
+    @Nullable
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
         checkExactlyOneWallpaperFlagSet(which);
-        return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which,
+        return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which,
                 mContext.getUserId());
     }
 
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 27270d9..d6592d5 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -765,10 +765,6 @@
     @Nullable
     public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
             @Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
-        if (mContext.checkSelfPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
-                != PackageManager.PERMISSION_GRANTED) {
-            Log.w(TAG, "Only allowed for recents.");
-        }
         logErrorForInvalidProfileAccess(user);
         if (DEBUG) {
             Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index a89d17b..50be5c4 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -91,7 +91,7 @@
     /**
      * The version name of this package, as specified by the &lt;manifest&gt;
      * tag's {@link android.R.styleable#AndroidManifest_versionName versionName}
-     * attribute.
+     * attribute, or null if there was none.
      */
     public String versionName;
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 56f6f82..d4e231b 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2565,9 +2565,9 @@
          * Sets the state of permissions for the package at installation.
          * <p/>
          * Granting any runtime permissions require the
-         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be
-         * held by the caller. Revoking runtime permissions is not allowed, even during app update
-         * sessions.
+         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS
+         * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime
+         * permissions is not allowed, even during app update sessions.
          * <p/>
          * Holders without the permission are allowed to change the following special permissions:
          * <p/>
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 9140d02..c2a0062 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -355,7 +355,11 @@
      * Sets a list of all user configurable credential providers registered on the system. This API
      * is intended for settings apps.
      *
-     * @param providers the list of enabled providers
+     * @param primaryProviders the primary providers that user selected for saving credentials. In
+     *                         the most case, there should be only one primary provider, However,
+     *                         if there are more than one CredentialProviderService in the same APK,
+     *                         they should be passed in altogether.
+     * @param providers the list of enabled providers.
      * @param userId the user ID to configure credential manager for
      * @param executor the callback will take place on this {@link Executor}
      * @param callback the callback invoked when the request succeeds or fails
@@ -363,6 +367,7 @@
      */
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public void setEnabledProviders(
+            @NonNull List<String> primaryProviders,
             @NonNull List<String> providers,
             int userId,
             @CallbackExecutor @NonNull Executor executor,
@@ -370,9 +375,11 @@
         requireNonNull(executor, "executor must not be null");
         requireNonNull(callback, "callback must not be null");
         requireNonNull(providers, "providers must not be null");
+        requireNonNull(primaryProviders, "primaryProviders must not be null");
 
         try {
             mService.setEnabledProviders(
+                    primaryProviders,
                     providers, userId, new SetEnabledProvidersTransport(executor, callback));
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index c224f01..d66b8f0 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -46,6 +46,7 @@
     @Nullable private CharSequence mSettingsSubtitle = null;
     private final boolean mIsSystemProvider;
     private final boolean mIsEnabled;
+    private final boolean mIsPrimary;
 
     /**
      * Constructs an information instance of the credential provider.
@@ -58,6 +59,7 @@
         mIsSystemProvider = builder.mIsSystemProvider;
         mSettingsSubtitle = builder.mSettingsSubtitle;
         mIsEnabled = builder.mIsEnabled;
+        mIsPrimary = builder.mIsPrimary;
         mOverrideLabel = builder.mOverrideLabel;
     }
 
@@ -108,6 +110,15 @@
         return mIsEnabled;
     }
 
+    /**
+     * Returns whether the provider is set as primary by the user.
+     *
+     * @hide
+     */
+    public boolean isPrimary() {
+        return mIsPrimary;
+    }
+
     /** Returns the settings subtitle. */
     @Nullable
     public CharSequence getSettingsSubtitle() {
@@ -125,6 +136,7 @@
         dest.writeTypedObject(mServiceInfo, flags);
         dest.writeBoolean(mIsSystemProvider);
         dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mIsPrimary);
         TextUtils.writeToParcel(mOverrideLabel, dest, flags);
         TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
 
@@ -149,6 +161,9 @@
                 + "isEnabled="
                 + mIsEnabled
                 + ", "
+                + "isPrimary="
+                + mIsPrimary
+                + ", "
                 + "overrideLabel="
                 + mOverrideLabel
                 + ", "
@@ -164,6 +179,7 @@
         mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
         mIsSystemProvider = in.readBoolean();
         mIsEnabled = in.readBoolean();
+        mIsPrimary = in.readBoolean();
         mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
 
@@ -193,6 +209,7 @@
         private boolean mIsSystemProvider = false;
         @Nullable private CharSequence mSettingsSubtitle = null;
         private boolean mIsEnabled = false;
+        private boolean mIsPrimary = false;
         @Nullable private CharSequence mOverrideLabel = null;
 
         /**
@@ -248,6 +265,20 @@
             return this;
         }
 
+        /**
+         * Sets whether it is set as primary by the user.
+         *
+         * <p>Primary provider will be used for saving credentials by default. In most cases, there
+         * should only one primary provider exist. However, if there are multiple credential
+         * providers exist in the same package, all of them will be marked as primary.
+         *
+         * @hide
+         */
+        public @NonNull Builder setPrimary(boolean isPrimary) {
+            mIsPrimary = isPrimary;
+            return this;
+        }
+
         /** Builds a new {@link CredentialProviderInfo} instance. */
         public @NonNull CredentialProviderInfo build() {
             return new CredentialProviderInfo(this);
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index b779c56..dec729f 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -47,7 +47,7 @@
 
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 
-    void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
+    void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
 
     void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);
 
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 9ebb058..3fc3be5 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -52,6 +52,12 @@
     @NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
     /** Type value for a getCredential request. */
     @NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
+    /** Type value for a getCredential request that utilizes the credential registry.
+     *
+     * @hide
+     **/
+    @NonNull public static final String TYPE_GET_VIA_REGISTRY =
+            "android.credentials.ui.TYPE_GET_VIA_REGISTRY";
     /** Type value for a createCredential request. */
     @NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
 
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 069c264..dcf1a47 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1136,7 +1136,10 @@
         Printer indentedPrinter = PrefixPrinter.create(printer, "    ");
         synchronized (mLock) {
             if (directories != null) {
-                directories.add(new File(mConfiguration.path).getParent());
+                String parent = new File(mConfiguration.path).getParent();
+                if (parent != null) {
+                    directories.add(parent);
+                }
             }
             boolean isCompatibilityWalEnabled = mConfiguration.isLegacyCompatibilityWalEnabled();
             printer.println("Connection pool for " + mConfiguration.path + ":");
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 6cd32ff..17bbe14 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -233,7 +233,7 @@
      */
     public static boolean useTouchpadNaturalScrolling(@NonNull Context context) {
         return Settings.System.getIntForUser(context.getContentResolver(),
-                Settings.System.TOUCHPAD_NATURAL_SCROLLING, 0, UserHandle.USER_CURRENT) == 1;
+                Settings.System.TOUCHPAD_NATURAL_SCROLLING, 1, UserHandle.USER_CURRENT) == 1;
     }
 
     /**
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index 22f5902..f9a2dbb 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -144,6 +144,9 @@
 
         int errorCode;
         switch (status) {
+            case RadioTuner.TUNER_RESULT_CANCELED:
+                errorCode = RadioTuner.ERROR_CANCELLED;
+                break;
             case RadioManager.STATUS_PERMISSION_DENIED:
             case RadioManager.STATUS_DEAD_OBJECT:
                 errorCode = RadioTuner.ERROR_SERVER_DIED;
@@ -152,10 +155,16 @@
             case RadioManager.STATUS_NO_INIT:
             case RadioManager.STATUS_BAD_VALUE:
             case RadioManager.STATUS_INVALID_OPERATION:
+            case RadioTuner.TUNER_RESULT_INTERNAL_ERROR:
+            case RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS:
+            case RadioTuner.TUNER_RESULT_INVALID_STATE:
+            case RadioTuner.TUNER_RESULT_NOT_SUPPORTED:
+            case RadioTuner.TUNER_RESULT_UNKNOWN_ERROR:
                 Log.i(TAG, "Got an error with no mapping to the legacy API (" + status
                         + "), doing a best-effort conversion to ERROR_SCAN_TIMEOUT");
             // fall through
             case RadioManager.STATUS_TIMED_OUT:
+            case RadioTuner.TUNER_RESULT_TIMEOUT:
             default:
                 errorCode = RadioTuner.ERROR_SCAN_TIMEOUT;
         }
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 21fe686..5c07fa4 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -232,7 +232,8 @@
                 recognitionEvent.captureAvailable, captureSession, recognitionEvent.captureDelayMs,
                 recognitionEvent.capturePreambleMs, recognitionEvent.triggerInData, audioFormat,
                 recognitionEvent.data,
-                recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis);
+                recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis,
+                aidlEvent.token);
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
@@ -254,7 +255,8 @@
                 recognitionEvent.common.captureDelayMs,
                 recognitionEvent.common.capturePreambleMs, recognitionEvent.common.triggerInData,
                 audioFormat,
-                recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis);
+                recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis,
+                aidlEvent.token);
     }
 
     // In case of a null input returns a non-null valid output.
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 6d43ddf..bfff4db 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -63,6 +63,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -1226,6 +1227,14 @@
         @ElapsedRealtimeLong
         public final long halEventReceivedMillis;
 
+        /**
+         * Binder token returned by {@link SoundTriggerModule#startRecognitionWithToken(
+         * int soundModelHandle, SoundTrigger.RecognitionConfig config)}
+         * @hide
+         */
+        public final IBinder token;
+
+
         /** @hide */
         @TestApi
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1235,14 +1244,16 @@
                 @ElapsedRealtimeLong long halEventReceivedMillis) {
             this(status, soundModelHandle, captureAvailable,
                     captureSession, captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
-                    data, status == RECOGNITION_STATUS_GET_STATE_RESPONSE, halEventReceivedMillis);
+                    data, status == RECOGNITION_STATUS_GET_STATE_RESPONSE, halEventReceivedMillis,
+                    null);
         }
 
         /** @hide */
         public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                 int captureSession, int captureDelayMs, int capturePreambleMs,
                 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
-                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis) {
+                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis,
+                IBinder token) {
             this.status = status;
             this.soundModelHandle = soundModelHandle;
             this.captureAvailable = captureAvailable;
@@ -1254,6 +1265,7 @@
             this.data = data != null ? data : new byte[0];
             this.recognitionStillActive = recognitionStillActive;
             this.halEventReceivedMillis = halEventReceivedMillis;
+            this.token = token;
         }
 
         /**
@@ -1311,6 +1323,16 @@
             return halEventReceivedMillis;
         }
 
+        /**
+         * Get token associated with this recognition session returned by
+         *{@link SoundTriggerModule#startRecognitionWithToken(
+         * int soundModelHandle, SoundTrigger.RecognitionConfig config)}
+         * @hide
+         */
+        public IBinder getToken() {
+            return token;
+        }
+
         /** @hide */
         public static final @android.annotation.NonNull Parcelable.Creator<RecognitionEvent> CREATOR
                 = new Parcelable.Creator<RecognitionEvent>() {
@@ -1346,9 +1368,10 @@
             byte[] data = in.readBlob();
             boolean recognitionStillActive = in.readBoolean();
             long halEventReceivedMillis = in.readLong();
+            IBinder token = in.readStrongBinder();
             return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
                     captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data,
-                    recognitionStillActive, halEventReceivedMillis);
+                    recognitionStillActive, halEventReceivedMillis, token);
         }
 
         /** @hide */
@@ -1376,6 +1399,7 @@
             dest.writeBlob(data);
             dest.writeBoolean(recognitionStillActive);
             dest.writeLong(halEventReceivedMillis);
+            dest.writeStrongBinder(token);
         }
         @Override
         public int hashCode() {
@@ -1396,6 +1420,7 @@
             result = prime * result + status;
             result = result + (recognitionStillActive ? 1289 : 1291);
             result = prime * result + Long.hashCode(halEventReceivedMillis);
+            result = prime * result +  Objects.hashCode(token);
             return result;
         }
 
@@ -1425,6 +1450,9 @@
             if (halEventReceivedMillis != other.halEventReceivedMillis) {
                 return false;
             }
+            if (!Objects.equals(token, other.token)) {
+                return false;
+            }
             if (status != other.status)
                 return false;
             if (triggerInData != other.triggerInData)
@@ -1462,8 +1490,8 @@
                     + ", data=" + (data == null ? 0 : data.length)
                     + ", recognitionStillActive=" + recognitionStillActive
                     + ", halEventReceivedMillis=" + halEventReceivedMillis
-                    + "]";
-        }
+                    + ", token=" + token
+                    + "]"; }
     }
 
     /**
@@ -1886,10 +1914,12 @@
                 int captureSession, int captureDelayMs, int capturePreambleMs,
                 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
                 @Nullable KeyphraseRecognitionExtra[] keyphraseExtras,
-                @ElapsedRealtimeLong long halEventReceivedMillis) {
+                @ElapsedRealtimeLong long halEventReceivedMillis,
+                IBinder token) {
             this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
                     capturePreambleMs, triggerInData, captureFormat, data, keyphraseExtras,
-                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE, halEventReceivedMillis);
+                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE, halEventReceivedMillis,
+                    token);
         }
 
         public KeyphraseRecognitionEvent(int status, int soundModelHandle,
@@ -1897,10 +1927,11 @@
                 int captureSession, int captureDelayMs, int capturePreambleMs,
                 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
                 @Nullable KeyphraseRecognitionExtra[] keyphraseExtras,
-                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis) {
+                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis,
+                IBinder token) {
             super(status, soundModelHandle, captureAvailable,
                     captureSession, captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
-                    data, recognitionStillActive, halEventReceivedMillis);
+                    data, recognitionStillActive, halEventReceivedMillis, token);
             this.keyphraseExtras =
                     keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
         }
@@ -1938,12 +1969,13 @@
             byte[] data = in.readBlob();
             boolean recognitionStillActive = in.readBoolean();
             long halEventReceivedMillis = in.readLong();
+            IBinder token = in.readStrongBinder();
             KeyphraseRecognitionExtra[] keyphraseExtras =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
             return new KeyphraseRecognitionEvent(status, soundModelHandle,
                     captureAvailable, captureSession, captureDelayMs, capturePreambleMs,
                     triggerInData, captureFormat, data, keyphraseExtras, recognitionStillActive,
-                    halEventReceivedMillis);
+                    halEventReceivedMillis, token);
         }
 
         @Override
@@ -1966,6 +1998,7 @@
             dest.writeBlob(data);
             dest.writeBoolean(recognitionStillActive);
             dest.writeLong(halEventReceivedMillis);
+            dest.writeStrongBinder(token);
             dest.writeTypedArray(keyphraseExtras, flags);
         }
 
@@ -2015,6 +2048,7 @@
                     + ", data=" + (data == null ? 0 : data.length)
                     + ", recognitionStillActive=" + recognitionStillActive
                     + ", halEventReceivedMillis=" + halEventReceivedMillis
+                    + ", token=" + token
                     + "]";
         }
     }
@@ -2030,20 +2064,23 @@
         public GenericRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                 int captureSession, int captureDelayMs, int capturePreambleMs,
                 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
-                @ElapsedRealtimeLong long halEventReceivedMillis) {
+                @ElapsedRealtimeLong long halEventReceivedMillis,
+                IBinder token) {
             this(status, soundModelHandle, captureAvailable,
                     captureSession, captureDelayMs,
                     capturePreambleMs, triggerInData, captureFormat, data,
-                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE, halEventReceivedMillis);
+                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE,
+                    halEventReceivedMillis, token);
         }
 
         public GenericRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                 int captureSession, int captureDelayMs, int capturePreambleMs,
                 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
-                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis) {
+                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis,
+                IBinder token) {
             super(status, soundModelHandle, captureAvailable,
                     captureSession, captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
-                    data, recognitionStillActive, halEventReceivedMillis);
+                    data, recognitionStillActive, halEventReceivedMillis, token);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<GenericRecognitionEvent> CREATOR
@@ -2062,7 +2099,7 @@
             return new GenericRecognitionEvent(event.status, event.soundModelHandle,
                     event.captureAvailable, event.captureSession, event.captureDelayMs,
                     event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data,
-                    event.recognitionStillActive, event.halEventReceivedMillis);
+                    event.recognitionStillActive, event.halEventReceivedMillis, event.token);
         }
 
         @Override
@@ -2092,7 +2129,7 @@
      *
      * @hide
      */
-    static int handleException(Exception e) {
+    public static int handleException(Exception e) {
         Log.w(TAG, "Exception caught", e);
         if (e instanceof RemoteException) {
             return STATUS_DEAD_OBJECT;
@@ -2269,7 +2306,7 @@
         Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
         try {
             return new SoundTriggerModule(getService(), moduleId, listener, looper,
-                    middlemanIdentity, originatorIdentity);
+                    middlemanIdentity, originatorIdentity, false);
         } catch (Exception e) {
             Log.e(TAG, "", e);
             return null;
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 5cdbe23..8813a17 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -83,7 +83,8 @@
      */
     public SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
             int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
-            @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity) {
+            @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity,
+            boolean isTrusted) {
         mId = moduleId;
         mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
 
@@ -91,7 +92,8 @@
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 mService = service.attachAsMiddleman(moduleId, middlemanIdentity,
                         originatorIdentity,
-                        mEventHandlerDelegate);
+                        mEventHandlerDelegate,
+                        isTrusted);
             }
             mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
         } catch (RemoteException e) {
@@ -247,6 +249,16 @@
     }
 
     /**
+     * Same as above, but return a binder token associated with the session.
+     * @hide
+     */
+    public synchronized IBinder startRecognitionWithToken(int soundModelHandle,
+            SoundTrigger.RecognitionConfig config) throws RemoteException {
+        return mService.startRecognition(soundModelHandle,
+                ConversionUtil.api2aidlRecognitionConfig(config));
+    }
+
+    /**
      * Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
      * @param soundModelHandle The sound model handle to stop listening to
      * @return - {@link SoundTrigger#STATUS_OK} in case of success
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 2c31e32..94971b8 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -107,42 +107,22 @@
     private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
     private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
 
-    // System properties related to ANGLE and legacy GLES graphics drivers.
-    private static final String PROPERTY_EGL_SYSTEM_DRIVER = "ro.hardware.egl";
-    // TODO (b/224558229): Properly add this to the list of system properties for a device:
-    private static final String PROPERTY_EGL_LEGACY_DRIVER = "ro.hardware.egl_legacy";
-
     // Values for ANGLE_GL_DRIVER_ALL_ANGLE
     private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1;
     private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
-    private static final int ANGLE_GL_DRIVER_ALL_LEGACY = -1;
 
     // Values for ANGLE_GL_DRIVER_SELECTION_VALUES
     private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
-    private static final String ANGLE_GL_DRIVER_CHOICE_LEGACY = "legacy";
-    // The following value is a deprecated choice for "legacy"
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
 
-    // Values returned by getDriverForPackage() and getDefaultDriverToUse() (avoid returning
-    // strings for performance reasons)
-    private static final int ANGLE_GL_DRIVER_TO_USE_LEGACY = 0;
-    private static final int ANGLE_GL_DRIVER_TO_USE_ANGLE = 1;
-
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
     private GameManager mGameManager;
 
-    private boolean mAngleIsSystemDriver = false;
-    private boolean mNoLegacyDriver = false;
-    // When ANGLE is the system driver, this is the name of the legacy driver.
-    //
-    // TODO (b/224558229): This is temporarily set to a value that works for testing, until
-    // PROPERTY_EGL_LEGACY_DRIVER has been properly plumbed and this becomes broadly available.
-    private String mEglLegacyDriver = "mali";
-
     private int mAngleOptInIndex = -1;
+    private boolean mEnabledByGameMode = false;
 
     /**
      * Set up GraphicsEnvironment
@@ -159,24 +139,6 @@
         setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
-        // Determine if ANGLE is the system driver, as this will determine other logic
-        final String eglSystemDriver = SystemProperties.get(PROPERTY_EGL_SYSTEM_DRIVER);
-        Log.v(TAG, "GLES system driver is '" + eglSystemDriver + "'");
-        mAngleIsSystemDriver = eglSystemDriver.equals(ANGLE_DRIVER_NAME);
-        if (mAngleIsSystemDriver) {
-            // Lookup the legacy driver, to send down to the EGL loader
-            final String eglLegacyDriver = SystemProperties.get(PROPERTY_EGL_LEGACY_DRIVER);
-            if (eglLegacyDriver.isEmpty()) {
-                mNoLegacyDriver = true;
-                // TBD/TODO: Do we need this?:
-                mEglLegacyDriver = eglSystemDriver;
-            }
-        } else {
-            // TBD/TODO: Do we need this?:
-            mEglLegacyDriver = eglSystemDriver;
-        }
-        Log.v(TAG, "Legacy GLES driver is '" + mEglLegacyDriver + "'");
-
         // Setup ANGLE and pass down ANGLE details to the C++ code
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
         boolean useAngle = false;
@@ -185,10 +147,6 @@
                 useAngle = true;
                 setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
                         0, packageName, getVulkanVersion(pm));
-            } else if (mNoLegacyDriver) {
-                // TBD: The following should never happen--does it?
-                Log.e(TAG, "Unexpected problem with the ANGLE for use with: '" + packageName + "'");
-                useAngle = true;
             }
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -242,12 +200,10 @@
     private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return mAngleIsSystemDriver ? true : false;
+            return false;
         }
 
-        final int driverToUse = getDriverForPackage(context, coreSettings, packageName);
-        boolean yesOrNo = driverToUse == ANGLE_GL_DRIVER_TO_USE_ANGLE;
-        return yesOrNo;
+        return shouldUseAngleInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -455,43 +411,25 @@
         return ai;
     }
 
-    /**
-     * Return the appropriate "default" driver, unless overridden by isAngleEnabledByGameMode().
-     */
-    private int getDefaultDriverToUse(Context context, String packageName) {
-        if (mAngleIsSystemDriver || isAngleEnabledByGameMode(context, packageName)) {
-            return ANGLE_GL_DRIVER_TO_USE_ANGLE;
-        } else {
-            return ANGLE_GL_DRIVER_TO_USE_LEGACY;
-        }
-    }
-
     /*
      * Determine which GLES "driver" should be used for the package, taking into account the
      * following factors (in priority order):
      *
      * 1) The semi-global switch (i.e. Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE; which is set by
      *    the "angle_gl_driver_all_angle" setting; which forces a driver for all processes that
-     *    start after the Java run time is up), if it forces a choice; otherwise ...
+     *    start after the Java run time is up), if it forces a choice;
      * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and
      *    Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the
      *    “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it
      *    forces a choice;
-     *      - Workaround Note: ANGLE and Vulkan currently have issues with applications that use YUV
-     *        target functionality.  The ANGLE broadcast receiver code will apply a "deferlist" at
-     *        the first boot of a newly-flashed device.  However, there is a gap in time between
-     *        when applications can start and when the deferlist is applied.  For now, assume that
-     *        if ANGLE is the system driver and Settings.Global.ANGLE_DEFERLIST is empty, that the
-     *        deferlist has not yet been applied.  In this case, select the Legacy driver.
-     *    otherwise ...
-     * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ...
-     * 4) The global switch (i.e. use the system driver, whether ANGLE or legacy;
-     *    a.k.a. mAngleIsSystemDriver, which is set by the device’s “ro.hardware.egl” property)
-     *
-     * Factors 1 and 2 are decided by this method.  Factors 3 and 4 are decided by
-     * getDefaultDriverToUse().
+     * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
      */
-    private int getDriverForPackage(Context context, Bundle bundle, String packageName) {
+    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+        // Make sure we have a good package name
+        if (TextUtils.isEmpty(packageName)) {
+            return false;
+        }
+
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
         // should be forced on or off for "all appplications"
         final int allUseAngle;
@@ -504,16 +442,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return ANGLE_GL_DRIVER_TO_USE_ANGLE;
-        }
-        if (allUseAngle == ANGLE_GL_DRIVER_ALL_LEGACY) {
-            Log.v(TAG, "Disable ANGLE for all applications.");
-            return ANGLE_GL_DRIVER_TO_USE_LEGACY;
-        }
-
-        // Make sure we have a good package name
-        if (TextUtils.isEmpty(packageName)) {
-            return getDefaultDriverToUse(context, packageName);
+            return true;
         }
 
         // Get the per-application settings lists
@@ -522,61 +451,46 @@
                 contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS);
         final List<String> optInValues = getGlobalSettingsString(
                 contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES);
-        final List<String> angleDeferlist = getGlobalSettingsString(
-                contentResolver, bundle, Settings.Global.ANGLE_DEFERLIST);
         Log.v(TAG, "Currently set values for:");
-        Log.v(TAG, "    angle_gl_driver_selection_pkgs =" + optInPackages);
-        Log.v(TAG, "  angle_gl_driver_selection_values =" + optInValues);
+        Log.v(TAG, "  angle_gl_driver_selection_pkgs=" + optInPackages);
+        Log.v(TAG, "  angle_gl_driver_selection_values=" + optInValues);
 
-        // If ANGLE is the system driver AND the deferlist has not yet been applied, select the
-        // Legacy driver
-        if (mAngleIsSystemDriver && angleDeferlist.size() == 0) {
-            Log.v(TAG, "ANGLE deferlist (" + Settings.Global.ANGLE_DEFERLIST + ") has not been "
-                           + "applied, defaulting to legacy driver");
-            return ANGLE_GL_DRIVER_TO_USE_LEGACY;
-        }
+        mEnabledByGameMode = isAngleEnabledByGameMode(context, packageName);
 
         // Make sure we have good settings to use
         if (optInPackages.size() != optInValues.size()) {
-            Log.w(TAG,
+            Log.v(TAG,
                     "Global.Settings values are invalid: "
                         + "number of packages: "
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return getDefaultDriverToUse(context, packageName);
+            return mEnabledByGameMode;
         }
 
-        // See if this application is listed in the per-application settings lists
+        // See if this application is listed in the per-application settings list
         final int pkgIndex = getPackageIndex(packageName, optInPackages);
 
         if (pkgIndex < 0) {
-            // The application is NOT listed in the per-application settings lists; and so use the
-            // system driver (i.e. either ANGLE or the Legacy driver)
-            Log.v(TAG, "getDriverForPackage(): No per-application setting");
-            return getDefaultDriverToUse(context, packageName);
+            Log.v(TAG, packageName + " is not listed in per-application setting");
+            return mEnabledByGameMode;
         }
         mAngleOptInIndex = pkgIndex;
 
-        Log.v(TAG,
-                "getDriverForPackage(): using per-application switch: "
-                        + optInValues.get(pkgIndex));
-        // The application IS listed in the per-application settings lists; and so use the
-        // setting--choosing the current system driver if the setting is "default" (i.e. either
-        // ANGLE or the Legacy driver)
-        String rtnValue = optInValues.get(pkgIndex);
+        // The application IS listed in the per-application settings list; and so use the
+        // setting--choosing the current system driver if the setting is "default"
+        String optInValue = optInValues.get(pkgIndex);
         Log.v(TAG,
                 "ANGLE Developer option for '" + packageName + "' "
-                        + "set to: '" + rtnValue + "'");
-        if (rtnValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return ANGLE_GL_DRIVER_TO_USE_ANGLE;
-        } else if (rtnValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)
-                || rtnValue.equals(ANGLE_GL_DRIVER_CHOICE_LEGACY)) {
-            return ANGLE_GL_DRIVER_TO_USE_LEGACY;
+                        + "set to: '" + optInValue + "'");
+        if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
+            return true;
+        } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+            return false;
         } else {
             // The user either chose default or an invalid value; go with the default driver or what
-            // the game dashboard indicates
-            return getDefaultDriverToUse(context, packageName);
+            // the game mode indicates
+            return mEnabledByGameMode;
         }
     }
 
@@ -631,9 +545,7 @@
      * the C++ GraphicsEnv class.
      *
      * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be
-     * properly used.  Otherwise, GraphicsEnv::setLegacyDriverInfo() will be called to
-     * enable the legacy GLES driver (e.g. when ANGLE is the system driver) to be identified and
-     * used.
+     * properly used.
      *
      * @param context
      * @param bundle
@@ -646,7 +558,6 @@
             String packageName) {
 
         if (!shouldUseAngle(context, bundle, packageName)) {
-            setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver);
             return false;
         }
 
@@ -655,13 +566,13 @@
         // If the developer has specified a debug package over ADB, attempt to find it
         String anglePkgName = getAngleDebugPackage(context, bundle);
         if (!anglePkgName.isEmpty()) {
-            Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName);
+            Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName);
             try {
                 // Note the debug package does not have to be pre-installed
                 angleInfo = pm.getApplicationInfo(anglePkgName, 0);
             } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
-                setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver);
+                // If the debug package is specified but not found, abort.
+                Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
                 return false;
             }
         }
@@ -670,8 +581,7 @@
         if (angleInfo == null) {
             anglePkgName = getAnglePackageName(pm);
             if (TextUtils.isEmpty(anglePkgName)) {
-                Log.w(TAG, "Failed to find ANGLE package.");
-                setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver);
+                Log.v(TAG, "Failed to find ANGLE package.");
                 return false;
             }
 
@@ -681,8 +591,7 @@
                 angleInfo = pm.getApplicationInfo(anglePkgName,
                         PackageManager.MATCH_SYSTEM_ONLY);
             } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
-                setLegacyDriverInfo(packageName, mAngleIsSystemDriver, mEglLegacyDriver);
+                Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed");
                 return false;
             }
         }
@@ -697,14 +606,13 @@
                 + abi;
 
         if (DEBUG) {
-            Log.v(TAG, "ANGLE package libs: " + paths);
+            Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
         // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
         // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(
-                paths, packageName, mAngleIsSystemDriver, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
+        setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
 
         return true;
     }
@@ -994,9 +902,7 @@
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
     private static native void setAngleInfo(String path, String appPackage,
-            boolean angleIsSystemDriver, String devOptIn, String[] features);
-    private static native void setLegacyDriverInfo(
-            String appPackage, boolean angleIsSystemDriver, String legacyDriverName);
+            String devOptIn, String[] features);
     private static native boolean getShouldUseAngle(String packageName);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 4ce9184..ef39010 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -56,6 +56,7 @@
 
     /** @hide A user id to indicate the currently active user */
     @UnsupportedAppUsage
+    @TestApi
     public static final @UserIdInt int USER_CURRENT = -2;
 
     /** @hide A user handle to indicate the current user of the device */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5bcbaa1..8606687 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4224,7 +4224,8 @@
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS
     })
-    public List<UserInfo> getUsers() {
+    @TestApi
+    public @NonNull List<UserInfo> getUsers() {
         return getUsers(/*excludePartial= */ true, /* excludeDying= */ false,
                 /* excludePreCreated= */ true);
     }
@@ -4245,6 +4246,7 @@
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS
     })
+    @TestApi
     public @NonNull List<UserInfo> getAliveUsers() {
         return getUsers(/*excludePartial= */ true, /* excludeDying= */ true,
                 /* excludePreCreated= */ true);
@@ -4271,8 +4273,7 @@
      * Returns information for all users on this device, based on the filtering parameters.
      *
      * @deprecated Pre-created users are deprecated and no longer supported.
-     *             Use {@link #getUsers()}, {@link #getUsers(boolean)}, or {@link #getAliveUsers()}
-     *             instead.
+     *             Use {@link #getUsers()}, or {@link #getAliveUsers()} instead.
      * @hide
      */
     @Deprecated
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 77c0067..ac6b2b2 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -1293,7 +1293,8 @@
                 USER_MISSED_NO_VIBRATE,
                 USER_MISSED_CALL_SCREENING_SERVICE_SILENCED,
                 USER_MISSED_CALL_FILTERS_TIMEOUT,
-                USER_MISSED_NEVER_RANG
+                USER_MISSED_NEVER_RANG,
+                USER_MISSED_NOT_RUNNING
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface MissedReason {}
@@ -1391,6 +1392,13 @@
         public static final long USER_MISSED_NEVER_RANG = 1 << 23;
 
         /**
+         * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+         * the user receiving the call is not running (i.e. work profile paused).
+         * @hide
+         */
+        public static final long USER_MISSED_NOT_RUNNING = 1 << 24;
+
+        /**
          * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE},
          * indicates factors which may have lead the user to miss the call.
          * <P>Type: INTEGER</P>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8cdb568..73c29d4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3036,9 +3036,7 @@
 
         public void destroy() {
             try {
-                // If this process is the system server process, mArray is the same object as
-                // the memory int array kept inside SettingsProvider, so skipping the close()
-                if (!Settings.isInSystemServer() && !mArray.isClosed()) {
+                if (!mArray.isClosed()) {
                     mArray.close();
                 }
             } catch (IOException e) {
@@ -3218,8 +3216,9 @@
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
             final boolean isSelf = (userHandle == UserHandle.myUserId());
+            final boolean useCache = isSelf && !isInSystemServer();
             boolean needsGenerationTracker = false;
-            if (isSelf) {
+            if (useCache) {
                 synchronized (NameValueCache.this) {
                     final GenerationTracker generationTracker = mGenerationTrackers.get(name);
                     if (generationTracker != null) {
@@ -3365,9 +3364,12 @@
                                 }
                             }
                         } else {
-                            if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle
-                                    + " by " + UserHandle.myUserId()
-                                    + " so not updating cache");
+                            if (DEBUG || LOCAL_LOGV) {
+                                Log.i(TAG, "call-query of user " + userHandle
+                                        + " by " + UserHandle.myUserId()
+                                        + (isInSystemServer() ? " in system_server" : "")
+                                        + " so not updating cache");
+                            }
                         }
                         return value;
                     }
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 751c675..b196b06 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -75,13 +75,12 @@
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context          the context object
+     * @param context the context object
      * @param serviceComponent the serviceComponent of the provider service
-     * @param userId           the android userId for which the current process is running
+     * @param userId the android userId for which the current process is running
      * @param isSystemProvider whether this provider is a system provider
      * @throws PackageManager.NameNotFoundException If provider service is not found
-     * @throws SecurityException                    If provider does not require the relevant
-     *                                              permission
+     * @throws SecurityException If provider does not require the relevant permission
      */
     public static CredentialProviderInfo create(
             @NonNull Context context,
@@ -94,21 +93,20 @@
                 getServiceInfoOrThrow(serviceComponent, userId),
                 isSystemProvider,
                 /* disableSystemAppVerificationForTests= */ false,
-                /* isEnabled= */ false);
+                /* isEnabled= */ false,
+                /* isPrimary= */ false);
     }
 
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context                              the context object
-     * @param serviceInfo                          the service info for the provider app. This must
-     *                                             be retrieved from the
-     *                                             {@code PackageManager}
-     * @param isSystemProvider                     whether the provider app is a system provider
+     * @param context the context object
+     * @param serviceInfo the service info for the provider app. This must be retrieved from the
+     *     {@code PackageManager}
+     * @param isSystemProvider whether the provider app is a system provider
      * @param disableSystemAppVerificationForTests whether to disable system app permission
-     *                                             verification so that tests can install system
-     *                                             providers
-     * @param isEnabled                            whether the user enabled this provider
+     *     verification so that tests can install system providers
+     * @param isEnabled whether the user enabled this provider
      * @throws SecurityException If provider does not require the relevant permission
      */
     public static CredentialProviderInfo create(
@@ -116,7 +114,8 @@
             @NonNull ServiceInfo serviceInfo,
             boolean isSystemProvider,
             boolean disableSystemAppVerificationForTests,
-            boolean isEnabled)
+            boolean isEnabled,
+            boolean isPrimary)
             throws SecurityException {
         verifyProviderPermission(serviceInfo);
         if (isSystemProvider) {
@@ -131,6 +130,7 @@
         return populateMetadata(context, serviceInfo)
                 .setSystemProvider(isSystemProvider)
                 .setEnabled(isEnabled)
+                .setPrimary(isPrimary)
                 .build();
     }
 
@@ -168,7 +168,9 @@
             Slog.w(TAG, "Context is null in isSystemProviderWithValidPermission");
             return false;
         }
-        return PermissionUtils.hasPermission(context, serviceInfo.packageName,
+        return PermissionUtils.hasPermission(
+                context,
+                serviceInfo.packageName,
                 Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE);
     }
 
@@ -181,8 +183,11 @@
         if (disableSystemAppVerificationForTests) {
             Bundle metadata = serviceInfo.metaData;
             if (metadata == null) {
-                Slog.w(TAG, "metadata is null while reading "
-                        + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + serviceInfo);
+                Slog.w(
+                        TAG,
+                        "metadata is null while reading "
+                                + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: "
+                                + serviceInfo);
                 return false;
             }
             return metadata.getBoolean(
@@ -215,8 +220,10 @@
 
         // 3. Stop if we are missing data.
         if (resources == null) {
-            Slog.w(TAG, "Resources are null for the serviceInfo being processed: "
-                    + serviceInfo.getComponentName());
+            Slog.w(
+                    TAG,
+                    "Resources are null for the serviceInfo being processed: "
+                            + serviceInfo.getComponentName());
             return builder;
         }
 
@@ -408,7 +415,7 @@
                                 si,
                                 /* isSystemProvider= */ true,
                                 disableSystemAppVerificationForTests,
-                                enabledServices.contains(si.getComponentName()));
+                                enabledServices.contains(si.getComponentName()), false);
                 if (cpi.isSystemProvider()) {
                     providerInfos.add(cpi);
                 } else {
@@ -446,7 +453,8 @@
             @NonNull Context context,
             int userId,
             int providerFilter,
-            Set<ComponentName> enabledServices) {
+            Set<ComponentName> enabledServices,
+            Set<String> primaryServices) {
         requireNonNull(context, "context must not be null");
 
         // Get the device policy.
@@ -459,7 +467,11 @@
                         context, pp, disableSystemAppVerificationForTests, providerFilter);
         generator.addUserProviders(
                 getUserProviders(
-                        context, userId, disableSystemAppVerificationForTests, enabledServices));
+                        context,
+                        userId,
+                        disableSystemAppVerificationForTests,
+                        enabledServices,
+                        primaryServices));
         generator.addSystemProviders(
                 getAvailableSystemServices(
                         context, userId, disableSystemAppVerificationForTests, enabledServices));
@@ -475,7 +487,8 @@
             @NonNull Context context,
             int userId,
             int providerFilter,
-            Set<ComponentName> enabledServices) {
+            Set<ComponentName> enabledServices,
+            Set<String> primaryServices) {
         requireNonNull(context, "context must not be null");
 
         // Get the device policy.
@@ -488,7 +501,11 @@
                         context, pp, disableSystemAppVerificationForTests, providerFilter);
         generator.addUserProviders(
                 getUserProviders(
-                        context, userId, disableSystemAppVerificationForTests, enabledServices));
+                        context,
+                        userId,
+                        disableSystemAppVerificationForTests,
+                        enabledServices,
+                        primaryServices));
         generator.addSystemProviders(
                 getAvailableSystemServices(
                         context, userId, disableSystemAppVerificationForTests, enabledServices));
@@ -581,7 +598,8 @@
             @NonNull Context context,
             @UserIdInt int userId,
             boolean disableSystemAppVerificationForTests,
-            Set<ComponentName> enabledServices) {
+            Set<ComponentName> enabledServices,
+            Set<String> primaryServices) {
         final List<CredentialProviderInfo> services = new ArrayList<>();
         final List<ResolveInfo> resolveInfos =
                 context.getPackageManager()
@@ -603,7 +621,9 @@
                                 serviceInfo,
                                 /* isSystemProvider= */ false,
                                 disableSystemAppVerificationForTests,
-                                enabledServices.contains(serviceInfo.getComponentName()));
+                                enabledServices.contains(serviceInfo.getComponentName()),
+                                primaryServices.contains(
+                                        serviceInfo.getComponentName().flattenToString()));
                 if (!cpi.isSystemProvider()) {
                     services.add(cpi);
                 }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 17d54b9..14f050d 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -984,7 +984,8 @@
                         new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
                                 captureSession, captureDelayMs, capturePreambleMs, triggerInData,
                                 captureFormat, data, keyphraseRecognitionExtras.toArray(
-                                new KeyphraseRecognitionExtra[0]), halEventReceivedMillis),
+                                new KeyphraseRecognitionExtra[0]), halEventReceivedMillis,
+                                new Binder()),
                         mInternalCallback);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 8d84e44..230f511 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -184,6 +184,7 @@
 
     private static final long DIMMING_ANIMATION_DURATION_MS = 300L;
 
+    @GuardedBy("itself")
     private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>();
 
     private Handler mBackgroundHandler;
@@ -2514,10 +2515,12 @@
             // if they are visible, so we need to toggle the state to get their attention.
             if (!mEngine.mDestroyed) {
                 mEngine.detach();
-                for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
-                    if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) {
-                        engineWrapper.mEngine.doVisibilityChanged(false);
-                        engineWrapper.mEngine.doVisibilityChanged(true);
+                synchronized (mActiveEngines) {
+                    for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
+                        if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) {
+                            engineWrapper.mEngine.doVisibilityChanged(false);
+                            engineWrapper.mEngine.doVisibilityChanged(true);
+                        }
                     }
                 }
             }
@@ -2699,7 +2702,9 @@
             IWallpaperEngineWrapper engineWrapper =
                     new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType,
                             isPreview, reqWidth, reqHeight, padding, displayId, which);
-            mActiveEngines.put(windowToken, engineWrapper);
+            synchronized (mActiveEngines) {
+                mActiveEngines.put(windowToken, engineWrapper);
+            }
             if (DEBUG) {
                 Slog.v(TAG, "IWallpaperServiceWrapper Attaching window token " + windowToken);
             }
@@ -2708,7 +2713,10 @@
 
         @Override
         public void detach(IBinder windowToken) {
-            IWallpaperEngineWrapper engineWrapper = mActiveEngines.remove(windowToken);
+            IWallpaperEngineWrapper engineWrapper;
+            synchronized (mActiveEngines) {
+                engineWrapper = mActiveEngines.remove(windowToken);
+            }
             if (engineWrapper == null) {
                 Log.w(TAG, "Engine for window token " + windowToken + " already detached");
                 return;
@@ -2734,10 +2742,12 @@
     public void onDestroy() {
         Trace.beginSection("WPMS.onDestroy");
         super.onDestroy();
-        for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
-            engineWrapper.destroy();
+        synchronized (mActiveEngines) {
+            for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
+                engineWrapper.destroy();
+            }
+            mActiveEngines.clear();
         }
-        mActiveEngines.clear();
         if (mBackgroundThread != null) {
             // onDestroy might be called without a previous onCreate if WallpaperService was
             // instantiated manually. While this is a misuse of the API, some things break
@@ -2768,14 +2778,18 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter out, String[] args) {
         out.print("State of wallpaper "); out.print(this); out.println(":");
-        for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
-            Engine engine = engineWrapper.mEngine;
-            if (engine == null) {
-                Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached");
-                continue;
+        synchronized (mActiveEngines) {
+            for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
+                Engine engine = engineWrapper.mEngine;
+                if (engine == null) {
+                    Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached");
+                    continue;
+                }
+                out.print("  Engine ");
+                out.print(engine);
+                out.println(":");
+                engine.dump("    ", fd, out, args);
             }
-            out.print("  Engine "); out.print(engine); out.println(":");
-            engine.dump("    ", fd, out, args);
         }
     }
 }
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 196bac2..cd76754 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1106,6 +1106,16 @@
                     mTransformedTextUpdate.before = before;
                     mTransformedTextUpdate.after = after;
                 }
+                // When there is a transformed text, we have to reflow the DynamicLayout based on
+                // the transformed indices instead of the range in base text.
+                // For example,
+                //   base text:         abcd    >   abce
+                //   updated range:     where = 3, before = 1, after = 1
+                //   transformed text:  abxxcd  >   abxxce
+                //   updated range:     where = 5, before = 1, after = 1
+                //
+                // Because the transformedText is udapted simultaneously with the base text,
+                // the range must be transformed before the base text changes.
                 transformedText.originalToTransformed(mTransformedTextUpdate);
             }
         }
@@ -1113,9 +1123,20 @@
         public void onTextChanged(CharSequence s, int where, int before, int after) {
             final DynamicLayout dynamicLayout = mLayout.get();
             if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
-                where = mTransformedTextUpdate.where;
-                before = mTransformedTextUpdate.before;
-                after = mTransformedTextUpdate.after;
+                if (mTransformedTextUpdate != null && mTransformedTextUpdate.where >= 0) {
+                    where = mTransformedTextUpdate.where;
+                    before = mTransformedTextUpdate.before;
+                    after = mTransformedTextUpdate.after;
+                    // Set where to -1 so that we know if beforeTextChanged is called.
+                    mTransformedTextUpdate.where = -1;
+                } else {
+                    // onTextChanged is called without beforeTextChanged. Reflow the entire text.
+                    where = 0;
+                    // We can't get the before length from the text, use the line end of the
+                    // last line instead.
+                    before = dynamicLayout.getLineEnd(dynamicLayout.getLineCount() - 1);
+                    after = dynamicLayout.mDisplay.length();
+                }
             }
             reflow(s, where, before, after);
         }
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
index 0c933d9..59b80f3 100644
--- a/core/java/android/text/method/InsertModeTransformationMethod.java
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -37,6 +37,8 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
+import java.lang.reflect.Array;
+
 /**
  * The transformation method used by handwriting insert mode.
  * This transformation will insert a placeholder string to the original text at the given
@@ -309,26 +311,51 @@
                 return ArrayUtils.emptyArray(type);
             }
 
-            final T[] spansOriginal;
+            T[] spansOriginal = null;
             if (mSpannedOriginal != null) {
                 final int originalStart =
                         transformedToOriginal(start, OffsetMapping.MAP_STRATEGY_CURSOR);
                 final int originalEnd =
                         transformedToOriginal(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+                // We can't simply call SpannedString.getSpans(originalStart, originalEnd) here.
+                // When start == end SpannedString.getSpans returns spans whose spanEnd == start.
+                // For example,
+                //   text: abcd  span: [1, 3)
+                // getSpan(3, 3) will return the span [1, 3) but getSpan(3, 4) returns no span.
+                //
+                // This creates some special cases when originalStart == originalEnd.
+                // For example:
+                //   original text: abcd    span1: [1, 3) span2: [3, 4) span3: [3, 3)
+                //   transformed text: abc\n\nd    span1: [1, 3) span2: [5, 6) span3: [3, 3)
+                // Case 1:
+                // When start = 3 and end = 4, transformedText#getSpan(3, 4) should return span3.
+                // However, because originalStart == originalEnd == 3, originalText#getSpan(3, 3)
+                // returns span1, span2 and span3.
+                //
+                // Case 2:
+                // When start == end == 4, transformedText#getSpan(4, 4) should return nothing.
+                // However, because originalStart == originalEnd == 3, originalText#getSpan(3, 3)
+                // return span1, span2 and span3.
+                //
+                // Case 3:
+                // When start == end == 5, transformedText#getSpan(5, 5) should return span2.
+                // However, because originalStart == originalEnd == 3, originalText#getSpan(3, 3)
+                // return span1,  span2 and span3.
+                //
+                // To handle the issue, we need to filter out the invalid spans.
                 spansOriginal = mSpannedOriginal.getSpans(originalStart, originalEnd, type);
-            } else {
-                spansOriginal = null;
+                spansOriginal = ArrayUtils.filter(spansOriginal,
+                        size -> (T[]) Array.newInstance(type, size),
+                        span -> intersect(getSpanStart(span), getSpanEnd(span), start, end));
             }
 
-            final T[] spansPlaceholder;
+            T[] spansPlaceholder = null;
             if (mSpannedPlaceholder != null
                     && intersect(start, end, mEnd, mEnd + mPlaceholder.length())) {
-                final int placeholderStart = Math.max(start - mEnd, 0);
-                final int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length());
+                int placeholderStart = Math.max(start - mEnd, 0);
+                int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length());
                 spansPlaceholder =
                         mSpannedPlaceholder.getSpans(placeholderStart, placeholderEnd, type);
-            } else {
-                spansPlaceholder = null;
             }
 
             // TODO: sort the spans based on their priority.
@@ -340,7 +367,10 @@
             if (mSpannedOriginal != null) {
                 final int index = mSpannedOriginal.getSpanStart(tag);
                 if (index >= 0) {
-                    if (index < mEnd) {
+                    // When originalSpanStart == originalSpanEnd == mEnd, the span should be
+                    // considered "before" the placeholder text. So we return the originalSpanStart.
+                    if (index < mEnd
+                            || (index == mEnd && mSpannedOriginal.getSpanEnd(tag) == index)) {
                         return index;
                     }
                     return index + mPlaceholder.length();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9670735..705a2ce0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25074,7 +25074,7 @@
         int viewStateIndex = 0;
         if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
         if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
-        if (isFocused() && hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
+        if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
         if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
         if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
         if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 153bfde..3208b62 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -301,6 +301,14 @@
             SystemProperties.getBoolean("persist.wm.debug.caption_on_shell", true);
 
     /**
+     * Whether the client (system UI) is handling the transient gesture and the corresponding
+     * animation.
+     * @hide
+     */
+    public static final boolean CLIENT_TRANSIENT =
+            SystemProperties.getBoolean("persist.wm.debug.client_transient", false);
+
+    /**
      * Whether the client should compute the window frame on its own.
      * @hide
      */
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index 3a8e802..2f7adaa 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.Application;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -486,8 +487,11 @@
     public void autofillClientAuthenticate(int authenticationId, IntentSender intent,
             Intent fillInIntent, boolean authenticateInline) {
         try {
+            ActivityOptions activityOptions = ActivityOptions.makeBasic()
+                    .setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
             mActivity.startIntentSenderForResult(intent, AUTO_FILL_AUTH_WHO_PREFIX,
-                    authenticationId, fillInIntent, 0, 0, null);
+                    authenticationId, fillInIntent, 0, 0, activityOptions.toBundle());
         } catch (IntentSender.SendIntentException e) {
             Log.e(TAG, "authenticate() failed for intent:" + intent, e);
         }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index a6e9d4d..5d121ad 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1464,6 +1464,13 @@
         }
 
         synchronized (mLock) {
+            if (mAllTrackedViews.contains(id)) {
+                // The id is tracked and will not trigger pre-fill request again.
+                return;
+            }
+
+            // Add the id as tracked to avoid triggering fill request again and again.
+            mAllTrackedViews.add(id);
             if (mTrackedViews != null) {
                 // To support the fill dialog can show for the autofillable Views in
                 // different pages but in the same Activity. We need to reset the
@@ -4064,11 +4071,6 @@
         }
 
         void checkViewState(AutofillId id) {
-            if (mAllTrackedViews.contains(id)) {
-                return;
-            }
-            // Add the id as tracked to avoid triggering fill request again and again.
-            mAllTrackedViews.add(id);
             if (mHasNewTrackedView) {
                 return;
             }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3165654..c289506 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -726,6 +726,12 @@
                 mActions.get(i).visitUris(visitor);
             }
         }
+        if (mLandscape != null) {
+            mLandscape.visitUris(visitor);
+        }
+        if (mPortrait != null) {
+            mPortrait.visitUris(visitor);
+        }
     }
 
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 34fe935..6c84f35 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -11808,8 +11808,17 @@
     public boolean hasSelection() {
         final int selectionStart = getSelectionStart();
         final int selectionEnd = getSelectionEnd();
+        final int selectionMin;
+        final int selectionMax;
+        if (selectionStart < selectionEnd) {
+            selectionMin = selectionStart;
+            selectionMax = selectionEnd;
+        } else {
+            selectionMin = selectionEnd;
+            selectionMax = selectionStart;
+        }
 
-        return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
+        return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
     }
 
     String getSelectedText() {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index c769fb9..2b9db70 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -259,86 +259,88 @@
     public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
     public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
 
+    private static final int LAST_CUJ = CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
     // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
     @VisibleForTesting
-    public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
-            // This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
-            NO_STATSD_LOGGING, // This is deprecated.
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT,
-    };
+    public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
+
+    static {
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[1] = NO_STATSD_LOGGING; // This is deprecated.
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+    }
 
     private static class InstanceHolder {
         public static final InteractionJankMonitor INSTANCE =
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 617519b..fdcb87f 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -1468,6 +1468,11 @@
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
         final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
         mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+        if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
+            Slog.wtf(TAG, "Significantly earlier event written to battery history:"
+                    + " time=" + mHistoryLastWritten.time
+                    + " previous=" + mHistoryLastLastWritten.time);
+        }
         mHistoryLastWritten.tagsFirstOccurrence = hasTags;
         writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
         mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
@@ -1908,12 +1913,6 @@
             in.setDataPosition(curPos + bufSize);
         }
 
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** OLD mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
         mHistoryBaseTimeMs = historyBaseTime;
         if (DEBUG) {
             StringBuilder sb = new StringBuilder(128);
@@ -1922,11 +1921,10 @@
             Slog.i(TAG, sb.toString());
         }
 
-        // We are just arbitrarily going to insert 1 minute from the sample of
-        // the last run until samples in this run.
         if (mHistoryBaseTimeMs > 0) {
-            long oldnow = mClock.elapsedRealtime();
-            mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
+            long elapsedRealtimeMs = mClock.elapsedRealtime();
+            mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+            mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
             if (DEBUG) {
                 StringBuilder sb = new StringBuilder(128);
                 sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index c144503..f277635 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -305,10 +305,17 @@
     private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
     @GuardedBy("mLock")
     private boolean mEnabled;
+    private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+            this::updateProperties;
 
     // Wrapping this in a holder class achieves lazy loading behavior
     private static final class SLatencyTrackerHolder {
-        private static final LatencyTracker sLatencyTracker = new LatencyTracker();
+        private static final LatencyTracker sLatencyTracker;
+
+        static {
+            sLatencyTracker = new LatencyTracker();
+            sLatencyTracker.startListeningForLatencyTrackerConfigChanges();
+        }
     }
 
     public static LatencyTracker getInstance(Context context) {
@@ -319,31 +326,16 @@
      * Constructor for LatencyTracker
      *
      * <p>This constructor is only visible for test classes to inject their own consumer callbacks
+     *
+     * @param startListeningForPropertyChanges If set, constructor will register for device config
+     *                      property updates prior to returning. If not set,
+     *                      {@link #startListeningForLatencyTrackerConfigChanges} must be called
+     *                      to start listening.
      */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @VisibleForTesting
     public LatencyTracker() {
         mEnabled = DEFAULT_ENABLED;
-
-        final Context context = ActivityThread.currentApplication();
-        if (context != null
-                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
-            // Post initialization to the background in case we're running on the main thread.
-            BackgroundThread.getHandler().post(() -> this.updateProperties(
-                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
-                    BackgroundThread.getExecutor(), this::updateProperties);
-        } else {
-            if (DEBUG) {
-                if (context == null) {
-                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
-                } else {
-                    Log.d(TAG, "Initialized the LatencyTracker."
-                            + " (No READ_DEVICE_CONFIG permission to change configs)"
-                            + " enabled=" + mEnabled + ", package=" + context.getPackageName());
-                }
-            }
-        }
     }
 
     private void updateProperties(DeviceConfig.Properties properties) {
@@ -366,6 +358,54 @@
     }
 
     /**
+     * Test method to start listening to {@link DeviceConfig} properties changes.
+     *
+     * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for
+     * config updates.
+     *
+     * <p>This is not used for production usages of this class outside of testing as we are
+     * using a single static object.
+     */
+    @VisibleForTesting
+    public void startListeningForLatencyTrackerConfigChanges() {
+        final Context context = ActivityThread.currentApplication();
+        if (context != null
+                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
+            // Post initialization to the background in case we're running on the main thread.
+            BackgroundThread.getHandler().post(() -> this.updateProperties(
+                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
+            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
+                    BackgroundThread.getExecutor(), mOnPropertiesChangedListener);
+        } else {
+            if (DEBUG) {
+                if (context == null) {
+                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
+                } else {
+                    synchronized (mLock) {
+                        Log.d(TAG, "Initialized the LatencyTracker."
+                                + " (No READ_DEVICE_CONFIG permission to change configs)"
+                                + " enabled=" + mEnabled + ", package=" + context.getPackageName());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Test method to stop listening to {@link DeviceConfig} properties changes.
+     *
+     * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for
+     * config updates.
+     *
+     * <p>This is not used for production usages of this class outside of testing as we are
+     * using a single static object.
+     */
+    @VisibleForTesting
+    public void stopListeningForLatencyTrackerConfigChanges() {
+        DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+    }
+
+    /**
      * A helper method to translate action type to name.
      *
      * @param atomsProtoAction the action type defined in AtomsProto.java
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index d9152d6..01dbceb 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
 }
 
 void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
-                         jboolean angleIsSystemDriver, jstring devOptIn, jobjectArray featuresObj) {
+                         jstring devOptIn, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars appNameChars(env, appName);
     ScopedUtfChars devOptInChars(env, devOptIn);
@@ -74,18 +74,7 @@
     }
 
     android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
-                                                     angleIsSystemDriver, devOptInChars.c_str(),
-                                                     features);
-}
-
-void setLegacyDriverInfo_native(JNIEnv* env, jobject clazz, jstring appName,
-                                jboolean angleIsSystemDriver, jstring legacyDriverName) {
-    ScopedUtfChars appNameChars(env, appName);
-    ScopedUtfChars legacyDriverNameChars(env, legacyDriverName);
-
-    android::GraphicsEnv::getInstance().setLegacyDriverInfo(appNameChars.c_str(),
-                                                            angleIsSystemDriver,
-                                                            legacyDriverNameChars.c_str());
+                                                     devOptInChars.c_str(), features);
 }
 
 bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
@@ -135,10 +124,8 @@
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
         {"setAngleInfo",
-         "(Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
-        {"setLegacyDriverInfo", "(Ljava/lang/String;ZLjava/lang/String;)V",
-         reinterpret_cast<void*>(setLegacyDriverInfo_native)},
         {"getShouldUseAngle", "(Ljava/lang/String;)Z",
          reinterpret_cast<void*>(shouldUseAngle_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index 850755a..55995df 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -21,6 +21,7 @@
 #include <android_runtime/Log.h>
 #include <gui/DisplayInfo.h>
 #include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfosUpdate.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalFrame.h>
 #include <utils/Log.h>
@@ -91,8 +92,7 @@
     WindowInfosListener(JNIEnv* env, jobject listener)
           : mListener(env->NewWeakGlobalRef(listener)) {}
 
-    void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                              const std::vector<DisplayInfo>& displayInfos) override {
+    void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override {
         JNIEnv* env = AndroidRuntime::getJNIEnv();
         LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onWindowInfoChanged.");
 
@@ -103,8 +103,10 @@
             return;
         }
 
-        ScopedLocalRef<jobjectArray> jWindowHandlesArray(env, fromWindowInfos(env, windowInfos));
-        ScopedLocalRef<jobjectArray> jDisplayInfoArray(env, fromDisplayInfos(env, displayInfos));
+        ScopedLocalRef<jobjectArray> jWindowHandlesArray(env,
+                                                         fromWindowInfos(env, update.windowInfos));
+        ScopedLocalRef<jobjectArray> jDisplayInfoArray(env,
+                                                       fromDisplayInfos(env, update.displayInfos));
 
         env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged,
                             jWindowHandlesArray.get(), jDisplayInfoArray.get());
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index a776bd2..a950a79 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -23,7 +23,7 @@
 option java_multiple_files = true;
 
 /* Represents a file full of transition entries.
-   Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (TRNTRACE), such
+   Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such
    that it can be easily identified. */
 message TransitionTraceProto {
 
@@ -38,28 +38,24 @@
 
   // Must be the first field, set to value in MagicNumber
   required fixed64 magic_number = 1;
-  // Transitions that don't have a finish time are considered aborted
-  repeated Transition finished_transitions = 2;
-
-  // Additional debugging info only collected and dumped when explicitly requested to trace
-  repeated TransitionState transition_states = 3;
-  repeated TransitionInfo transition_info = 4;
+  repeated Transition transitions = 2;
 
   /* offset between real-time clock and elapsed time clock in nanoseconds.
    Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
-  optional fixed64 real_to_elapsed_time_offset_nanos = 5;
+  optional fixed64 real_to_elapsed_time_offset_nanos = 3;
 }
 
 message Transition {
-  optional int32 id = 1; // Not dumped in always on tracing
-  required uint64 start_transaction_id = 2;
-  required uint64 finish_transaction_id = 3;
-  required int64 create_time_ns = 4;
-  required int64 send_time_ns = 5;
-  optional int64 finish_time_ns = 6; // consider aborted if not provided
-  required int32 type = 7;
+  required int32 id = 1;
+  optional uint64 start_transaction_id = 2;
+  optional uint64 finish_transaction_id = 3;
+  optional int64 create_time_ns = 4;
+  optional int64 send_time_ns = 5;
+  optional int64 finish_time_ns = 6;
+  optional int32 type = 7;
   repeated Target targets = 8;
   optional int32 flags = 9;
+  optional int64 abort_time_ns = 10;
 }
 
 message Target {
@@ -68,40 +64,3 @@
   optional int32 window_id = 3;  // Not dumped in always on tracing
   optional int32 flags = 4;
 }
-
-message TransitionState {
-  enum State {
-    COLLECTING = 0;
-    PENDING = -1;
-    STARTED = 1;
-    PLAYING = 2;
-    ABORT = 3;
-    FINISHED = 4;
-  }
-
-  required int64 time_ns = 1;
-  required int32 transition_id = 2;
-  required int32 transition_type = 3;
-  required State state = 4;
-  required int32 flags = 5;
-  repeated ChangeInfo change = 6;
-  repeated com.android.server.wm.IdentifierProto participants = 7;
-}
-
-message ChangeInfo {
-  required com.android.server.wm.IdentifierProto window_identifier = 1;
-  required int32 transit_mode = 2;
-  required bool has_changed = 3;
-  required int32 change_flags = 4;
-  required int32 windowing_mode = 5;
-}
-
-message TransitionInfo {
-  required int32 transition_id = 1;
-  repeated TransitionInfoChange change = 2;
-}
-
-message TransitionInfoChange {
-  required int32 layer_id = 1;
-  required int32 mode = 2;
-}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fefa79f..7e0a36d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4976,11 +4976,11 @@
         android:protectionLevel="signature" />
 
     <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
-         <p>Protection level: internal|role
-         <p>Intended for use by ROLE_ASSISTANT only.
+         <p>Protection level: signature|role
+         <p>Intended for use by ROLE_ASSISTANT and signature apps only.
     -->
     <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
-                android:protectionLevel="internal|role"/>
+                android:protectionLevel="signature|role"/>
 
     <!-- Must be required by a {@link android.service.autofill.AutofillService},
          to ensure that only the system can bind to it.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7de36a71..6f7bc53 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2791,7 +2791,7 @@
             <flag name="noExcludeDescendants" value="0x8" />
         </attr>
 
-        <!-- Boolean that hints the Android System that the view is credntial and associated with
+        <!-- Boolean that hints the Android System that the view is credential and associated with
              CredentialManager -->
         <attr name="isCredential" format="boolean" />
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bf141b5..17d8402 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -774,11 +774,6 @@
          we rely on gravity to determine the effective orientation. -->
     <bool name="config_deskDockEnablesAccelerometer">true</bool>
 
-    <!-- Control whether nosensor and locked orientation requests are respected from the app when
-         config_deskDockEnablesAccelerometer is set to false.
-         TODO(b/274763533): Consider making true by default and removing this. -->
-    <bool name="config_deskRespectsNoSensorAndLockedWithoutAccelerometer">false</bool>
-
     <!-- Car dock behavior -->
 
     <!-- The number of degrees to rotate the display when the device is in a car dock.
@@ -6440,4 +6435,8 @@
     <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool>
     <!-- Whether to request the approval before commit sessions. -->
     <bool name="config_isPreApprovalRequestAvailable">true</bool>
+
+    <!-- Whether the AOSP support for app cloning building blocks is to be enabled for the
+         device. -->
+    <bool name="config_enableAppCloningBuildingBlocks">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c91e3cb..e3697bb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -480,6 +480,7 @@
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
   <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
+  <java-symbol type="bool" name="config_enableAppCloningBuildingBlocks" />
   <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
@@ -1698,7 +1699,6 @@
   <java-symbol type="bool" name="config_carDockEnablesAccelerometer" />
   <java-symbol type="bool" name="config_customUserSwitchUi" />
   <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" />
-  <java-symbol type="bool" name="config_deskRespectsNoSensorAndLockedWithoutAccelerometer" />
   <java-symbol type="bool" name="config_disableMenuKeyInLockScreen" />
   <java-symbol type="bool" name="config_enableCarDockHomeLaunch" />
   <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index c7b82b1..6a6a951 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -750,6 +750,15 @@
     }
 
     @Test
+    public void onTuneFailed_withCanceledResult() throws Exception {
+        mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_CANCELED, FM_SELECTOR);
+
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
+                RadioTuner.TUNER_RESULT_CANCELED, FM_SELECTOR);
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_CANCELLED);
+    }
+
+    @Test
     public void onProgramListChanged_forTunerCallbackAdapter() throws Exception {
         mTunerCallback.onProgramListChanged();
 
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index b0d5240..dc4c252 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -56,22 +56,20 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class CredentialManagerTest {
-    @Mock
-    private ICredentialManager mMockCredentialManagerService;
+    @Mock private ICredentialManager mMockCredentialManagerService;
 
-    @Mock
-    private Activity mMockActivity;
+    @Mock private Activity mMockActivity;
 
     private static final int TEST_USER_ID = 1;
     private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO =
-                new CredentialProviderInfo.Builder(new ServiceInfo())
-                        .setSystemProvider(true)
-                        .setOverrideLabel("test")
-                        .addCapabilities(Arrays.asList("passkey"))
-                        .setEnabled(true)
-                        .build();
+            new CredentialProviderInfo.Builder(new ServiceInfo())
+                    .setSystemProvider(true)
+                    .setOverrideLabel("test")
+                    .addCapabilities(Arrays.asList("passkey"))
+                    .setEnabled(true)
+                    .build();
     private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST =
-                Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
+            Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
 
     private GetCredentialRequest mGetRequest;
     private CreateCredentialRequest mCreateRequest;
@@ -112,27 +110,43 @@
 
     @Before
     public void setup() {
-        mGetRequest = new GetCredentialRequest.Builder(Bundle.EMPTY).addCredentialOption(
-                new CredentialOption(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY,
-                        Bundle.EMPTY, false)).build();
-        mCreateRequest = new CreateCredentialRequest.Builder(
-                Credential.TYPE_PASSWORD_CREDENTIAL,
-                Bundle.EMPTY, Bundle.EMPTY)
-                .setIsSystemProviderRequired(false)
-                .setAlwaysSendAppInfoToProvider(false)
-                .build();
+        mGetRequest =
+                new GetCredentialRequest.Builder(Bundle.EMPTY)
+                        .addCredentialOption(
+                                new CredentialOption(
+                                        Credential.TYPE_PASSWORD_CREDENTIAL,
+                                        Bundle.EMPTY,
+                                        Bundle.EMPTY,
+                                        false))
+                        .build();
+        mCreateRequest =
+                new CreateCredentialRequest.Builder(
+                                Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY, Bundle.EMPTY)
+                        .setIsSystemProviderRequired(false)
+                        .setAlwaysSendAppInfoToProvider(false)
+                        .build();
         mClearRequest = new ClearCredentialStateRequest(Bundle.EMPTY);
 
-        final Slice slice = new Slice.Builder(Uri.parse("foo://bar"), null).addText("some text",
-                null, List.of(Slice.HINT_TITLE)).build();
-        mRegisterRequest = new RegisterCredentialDescriptionRequest(
-                new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
-                        new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
-                        List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
-        mUnregisterRequest = new UnregisterCredentialDescriptionRequest(
-                new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
-                        new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
-                        List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
+        final Slice slice =
+                new Slice.Builder(Uri.parse("foo://bar"), null)
+                        .addText("some text", null, List.of(Slice.HINT_TITLE))
+                        .build();
+        mRegisterRequest =
+                new RegisterCredentialDescriptionRequest(
+                        new CredentialDescription(
+                                Credential.TYPE_PASSWORD_CREDENTIAL,
+                                new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
+                                List.of(
+                                        new CredentialEntry(
+                                                Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
+        mUnregisterRequest =
+                new UnregisterCredentialDescriptionRequest(
+                        new CredentialDescription(
+                                Credential.TYPE_PASSWORD_CREDENTIAL,
+                                new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
+                                List.of(
+                                        new CredentialEntry(
+                                                Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
 
         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
         mCredentialManager = new CredentialManager(context, mMockCredentialManagerService);
@@ -143,56 +157,63 @@
     @Test
     public void testGetCredential_nullRequest() {
         GetCredentialRequest nullRequest = null;
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.getCredential(mMockActivity, nullRequest, null, mExecutor,
-                        result -> {
-                        }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.getCredential(
+                                mMockActivity, nullRequest, null, mExecutor, result -> {}));
     }
 
     @Test
     public void testGetCredential_nullActivity() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.getCredential(null, mGetRequest, null, mExecutor,
-                        result -> {
-                        }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.getCredential(
+                                null, mGetRequest, null, mExecutor, result -> {}));
     }
 
     @Test
     public void testGetCredential_nullExecutor() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null,
-                        result -> {
-                        }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.getCredential(
+                                mMockActivity, mGetRequest, null, null, result -> {}));
     }
 
     @Test
     public void testGetCredential_nullCallback() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null,
-                        null));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.getCredential(
+                                mMockActivity, mGetRequest, null, null, null));
     }
 
     @Test
     public void testGetCredential_noCredential() throws RemoteException {
-        ArgumentCaptor<IGetCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
-                IGetCredentialCallback.class);
-        ArgumentCaptor<GetCredentialException> errorCaptor = ArgumentCaptor.forClass(
-                GetCredentialException.class);
+        ArgumentCaptor<IGetCredentialCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IGetCredentialCallback.class);
+        ArgumentCaptor<GetCredentialException> errorCaptor =
+                ArgumentCaptor.forClass(GetCredentialException.class);
 
-        OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock(
-                OutcomeReceiver.class);
+        OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback =
+                mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(),
-                any())).thenReturn(mock(ICancellationSignal.class));
+        when(mMockCredentialManagerService.executeGetCredential(
+                        any(), callbackCaptor.capture(), any()))
+                .thenReturn(mock(ICancellationSignal.class));
         mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback);
         verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
 
-        callbackCaptor.getValue().onError(GetCredentialException.TYPE_NO_CREDENTIAL,
-                "no credential found");
+        callbackCaptor
+                .getValue()
+                .onError(GetCredentialException.TYPE_NO_CREDENTIAL, "no credential found");
         verify(callback).onError(errorCaptor.capture());
 
-        assertThat(errorCaptor.getValue().getType()).isEqualTo(
-                GetCredentialException.TYPE_NO_CREDENTIAL);
+        assertThat(errorCaptor.getValue().getType())
+                .isEqualTo(GetCredentialException.TYPE_NO_CREDENTIAL);
     }
 
     @Test
@@ -200,9 +221,8 @@
         final CancellationSignal cancellation = new CancellationSignal();
         cancellation.cancel();
 
-        mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor,
-                result -> {
-                });
+        mCredentialManager.getCredential(
+                mMockActivity, mGetRequest, cancellation, mExecutor, result -> {});
 
         verify(mMockCredentialManagerService, never()).executeGetCredential(any(), any(), any());
     }
@@ -212,14 +232,14 @@
         final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
         final CancellationSignal cancellation = new CancellationSignal();
 
-        OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock(
-                OutcomeReceiver.class);
+        OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback =
+                mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.executeGetCredential(any(), any(), any())).thenReturn(
-                serviceSignal);
+        when(mMockCredentialManagerService.executeGetCredential(any(), any(), any()))
+                .thenReturn(serviceSignal);
 
-        mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor,
-                callback);
+        mCredentialManager.getCredential(
+                mMockActivity, mGetRequest, cancellation, mExecutor, callback);
 
         verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
 
@@ -231,16 +251,17 @@
     public void testGetCredential_success() throws RemoteException {
         final Credential cred = new Credential(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY);
 
-        ArgumentCaptor<IGetCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
-                IGetCredentialCallback.class);
-        ArgumentCaptor<GetCredentialResponse> responseCaptor = ArgumentCaptor.forClass(
-                GetCredentialResponse.class);
+        ArgumentCaptor<IGetCredentialCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IGetCredentialCallback.class);
+        ArgumentCaptor<GetCredentialResponse> responseCaptor =
+                ArgumentCaptor.forClass(GetCredentialResponse.class);
 
-        OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock(
-                OutcomeReceiver.class);
+        OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback =
+                mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(),
-                any())).thenReturn(mock(ICancellationSignal.class));
+        when(mMockCredentialManagerService.executeGetCredential(
+                        any(), callbackCaptor.capture(), any()))
+                .thenReturn(mock(ICancellationSignal.class));
         mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback);
         verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
 
@@ -252,33 +273,38 @@
 
     @Test
     public void testCreateCredential_nullRequest() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.createCredential(mMockActivity, null, null, mExecutor,
-                        result -> {
-                        }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.createCredential(
+                                mMockActivity, null, null, mExecutor, result -> {}));
     }
 
     @Test
     public void testCreateCredential_nullActivity() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.createCredential(null, mCreateRequest, null, mExecutor,
-                        result -> {
-                        }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.createCredential(
+                                null, mCreateRequest, null, mExecutor, result -> {}));
     }
 
     @Test
     public void testCreateCredential_nullExecutor() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, null,
-                        result -> {
-                        }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.createCredential(
+                                mMockActivity, mCreateRequest, null, null, result -> {}));
     }
 
     @Test
     public void testCreateCredential_nullCallback() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null,
-                        mExecutor, null));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.createCredential(
+                                mMockActivity, mCreateRequest, null, mExecutor, null));
     }
 
     @Test
@@ -286,9 +312,8 @@
         final CancellationSignal cancellation = new CancellationSignal();
         cancellation.cancel();
 
-        mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor,
-                result -> {
-                });
+        mCredentialManager.createCredential(
+                mMockActivity, mCreateRequest, cancellation, mExecutor, result -> {});
 
         verify(mMockCredentialManagerService, never()).executeCreateCredential(any(), any(), any());
     }
@@ -298,17 +323,17 @@
         final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
         final CancellationSignal cancellation = new CancellationSignal();
 
-        OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock(
-                OutcomeReceiver.class);
+        OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback =
+                mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any())).thenReturn(
-                serviceSignal);
+        when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any()))
+                .thenReturn(serviceSignal);
 
-        mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor,
-                callback);
+        mCredentialManager.createCredential(
+                mMockActivity, mCreateRequest, cancellation, mExecutor, callback);
 
-        verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
-                eq(mPackageName));
+        verify(mMockCredentialManagerService)
+                .executeCreateCredential(any(), any(), eq(mPackageName));
 
         cancellation.cancel();
         verify(serviceSignal).cancel();
@@ -316,26 +341,27 @@
 
     @Test
     public void testCreateCredential_failed() throws RemoteException {
-        ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
-                ICreateCredentialCallback.class);
-        ArgumentCaptor<CreateCredentialException> errorCaptor = ArgumentCaptor.forClass(
-                CreateCredentialException.class);
+        ArgumentCaptor<ICreateCredentialCallback> callbackCaptor =
+                ArgumentCaptor.forClass(ICreateCredentialCallback.class);
+        ArgumentCaptor<CreateCredentialException> errorCaptor =
+                ArgumentCaptor.forClass(CreateCredentialException.class);
 
-        OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock(
-                OutcomeReceiver.class);
+        OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback =
+                mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(),
-                any())).thenReturn(mock(ICancellationSignal.class));
-        mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor,
-                callback);
-        verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
-                eq(mPackageName));
+        when(mMockCredentialManagerService.executeCreateCredential(
+                        any(), callbackCaptor.capture(), any()))
+                .thenReturn(mock(ICancellationSignal.class));
+        mCredentialManager.createCredential(
+                mMockActivity, mCreateRequest, null, mExecutor, callback);
+        verify(mMockCredentialManagerService)
+                .executeCreateCredential(any(), any(), eq(mPackageName));
 
         callbackCaptor.getValue().onError(CreateCredentialException.TYPE_UNKNOWN, "unknown error");
         verify(callback).onError(errorCaptor.capture());
 
-        assertThat(errorCaptor.getValue().getType()).isEqualTo(
-                CreateCredentialException.TYPE_UNKNOWN);
+        assertThat(errorCaptor.getValue().getType())
+                .isEqualTo(CreateCredentialException.TYPE_UNKNOWN);
     }
 
     @Test
@@ -343,20 +369,21 @@
         final Bundle responseData = new Bundle();
         responseData.putString("foo", "bar");
 
-        ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
-                ICreateCredentialCallback.class);
-        ArgumentCaptor<CreateCredentialResponse> responseCaptor = ArgumentCaptor.forClass(
-                CreateCredentialResponse.class);
+        ArgumentCaptor<ICreateCredentialCallback> callbackCaptor =
+                ArgumentCaptor.forClass(ICreateCredentialCallback.class);
+        ArgumentCaptor<CreateCredentialResponse> responseCaptor =
+                ArgumentCaptor.forClass(CreateCredentialResponse.class);
 
-        OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock(
-                OutcomeReceiver.class);
+        OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback =
+                mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(),
-                any())).thenReturn(mock(ICancellationSignal.class));
-        mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor,
-                callback);
-        verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
-                eq(mPackageName));
+        when(mMockCredentialManagerService.executeCreateCredential(
+                        any(), callbackCaptor.capture(), any()))
+                .thenReturn(mock(ICancellationSignal.class));
+        mCredentialManager.createCredential(
+                mMockActivity, mCreateRequest, null, mExecutor, callback);
+        verify(mMockCredentialManagerService)
+                .executeCreateCredential(any(), any(), eq(mPackageName));
 
         callbackCaptor.getValue().onResponse(new CreateCredentialResponse(responseData));
         verify(callback).onResult(responseCaptor.capture());
@@ -366,23 +393,27 @@
 
     @Test
     public void testClearCredentialState_nullRequest() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.clearCredentialState(null, null, mExecutor, result -> {
-                }));
+        assertThrows(
+                NullPointerException.class,
+                () -> mCredentialManager.clearCredentialState(null, null, mExecutor, result -> {}));
     }
 
     @Test
     public void testClearCredentialState_nullExecutor() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.clearCredentialState(mClearRequest, null, null, result -> {
-                }));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.clearCredentialState(
+                                mClearRequest, null, null, result -> {}));
     }
 
     @Test
     public void testClearCredentialState_nullCallback() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor,
-                        null));
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.clearCredentialState(
+                                mClearRequest, null, mExecutor, null));
     }
 
     @Test
@@ -390,8 +421,8 @@
         final CancellationSignal cancellation = new CancellationSignal();
         cancellation.cancel();
 
-        mCredentialManager.clearCredentialState(mClearRequest, cancellation, mExecutor, result -> {
-        });
+        mCredentialManager.clearCredentialState(
+                mClearRequest, cancellation, mExecutor, result -> {});
 
         verify(mMockCredentialManagerService, never()).clearCredentialState(any(), any(), any());
     }
@@ -403,8 +434,8 @@
 
         OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.clearCredentialState(any(), any(), any())).thenReturn(
-                serviceSignal);
+        when(mMockCredentialManagerService.clearCredentialState(any(), any(), any()))
+                .thenReturn(serviceSignal);
 
         mCredentialManager.clearCredentialState(mClearRequest, cancellation, mExecutor, callback);
 
@@ -416,35 +447,38 @@
 
     @Test
     public void testClearCredential_failed() throws RemoteException {
-        ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = ArgumentCaptor.forClass(
-                IClearCredentialStateCallback.class);
-        ArgumentCaptor<ClearCredentialStateException> errorCaptor = ArgumentCaptor.forClass(
-                ClearCredentialStateException.class);
+        ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IClearCredentialStateCallback.class);
+        ArgumentCaptor<ClearCredentialStateException> errorCaptor =
+                ArgumentCaptor.forClass(ClearCredentialStateException.class);
 
         OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.clearCredentialState(any(), callbackCaptor.capture(),
-                any())).thenReturn(mock(ICancellationSignal.class));
+        when(mMockCredentialManagerService.clearCredentialState(
+                        any(), callbackCaptor.capture(), any()))
+                .thenReturn(mock(ICancellationSignal.class));
         mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, callback);
         verify(mMockCredentialManagerService).clearCredentialState(any(), any(), eq(mPackageName));
 
-        callbackCaptor.getValue().onError(ClearCredentialStateException.TYPE_UNKNOWN,
-                "unknown error");
+        callbackCaptor
+                .getValue()
+                .onError(ClearCredentialStateException.TYPE_UNKNOWN, "unknown error");
         verify(callback).onError(errorCaptor.capture());
 
-        assertThat(errorCaptor.getValue().getType()).isEqualTo(
-                ClearCredentialStateException.TYPE_UNKNOWN);
+        assertThat(errorCaptor.getValue().getType())
+                .isEqualTo(ClearCredentialStateException.TYPE_UNKNOWN);
     }
 
     @Test
     public void testClearCredential_success() throws RemoteException {
-        ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = ArgumentCaptor.forClass(
-                IClearCredentialStateCallback.class);
+        ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IClearCredentialStateCallback.class);
 
         OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class);
 
-        when(mMockCredentialManagerService.clearCredentialState(any(), callbackCaptor.capture(),
-                any())).thenReturn(mock(ICancellationSignal.class));
+        when(mMockCredentialManagerService.clearCredentialState(
+                        any(), callbackCaptor.capture(), any()))
+                .thenReturn(mock(ICancellationSignal.class));
         mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, callback);
         verify(mMockCredentialManagerService).clearCredentialState(any(), any(), eq(mPackageName));
 
@@ -464,27 +498,32 @@
 
     @Test
     public void testGetCredentialProviderServices_systemProviders() throws RemoteException {
-        verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+        verifyGetCredentialProviderServices(
+                CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
     }
 
     @Test
     public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException {
-        verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
+        verifyGetCredentialProviderServicesForTesting(
+                CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
     }
 
     @Test
     public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException {
-        verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
+        verifyGetCredentialProviderServicesForTesting(
+                CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
     }
 
     @Test
-    public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException {
-        verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+    public void testGetCredentialProviderServicesForTesting_systemProviders()
+            throws RemoteException {
+        verifyGetCredentialProviderServicesForTesting(
+                CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
     }
 
     private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException {
-        when(mMockCredentialManagerService.getCredentialProviderServices(
-                TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+        when(mMockCredentialManagerService.getCredentialProviderServices(TEST_USER_ID, testFilter))
+                .thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
 
         List<CredentialProviderInfo> output =
                 mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter);
@@ -492,9 +531,10 @@
         assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
     }
 
-    private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException {
-        when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(
-                testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+    private void verifyGetCredentialProviderServicesForTesting(int testFilter)
+            throws RemoteException {
+        when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(testFilter))
+                .thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
 
         List<CredentialProviderInfo> output =
                 mCredentialManager.getCredentialProviderServicesForTesting(testFilter);
@@ -504,41 +544,45 @@
 
     @Test
     public void testSetEnabledProviders_nullProviders() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.setEnabledProviders(null, 0, mExecutor, response -> {
-                }));
-
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.setEnabledProviders(
+                                null, null, 0, mExecutor, response -> {}));
     }
 
     @Test
     public void testSetEnabledProviders_nullExecutor() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.setEnabledProviders(List.of("foo"), 0, null, response -> {
-                }));
-
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.setEnabledProviders(
+                                List.of("foo"), List.of("foo"), 0, null, response -> {}));
     }
 
     @Test
     public void testSetEnabledProviders_nullCallback() {
-        assertThrows(NullPointerException.class,
-                () -> mCredentialManager.setEnabledProviders(List.of("foo"), 0, mExecutor, null));
-
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        mCredentialManager.setEnabledProviders(
+                                List.of("foo"), List.of("foo"), 0, mExecutor, null));
     }
 
     @Test
     public void testSetEnabledProviders_failed() throws RemoteException {
         OutcomeReceiver<Void, SetEnabledProvidersException> callback = mock(OutcomeReceiver.class);
 
-        ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
-                ISetEnabledProvidersCallback.class);
-        ArgumentCaptor<SetEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass(
-                SetEnabledProvidersException.class);
+        ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor =
+                ArgumentCaptor.forClass(ISetEnabledProvidersCallback.class);
+        ArgumentCaptor<SetEnabledProvidersException> errorCaptor =
+                ArgumentCaptor.forClass(SetEnabledProvidersException.class);
 
         final List<String> providers = List.of("foo", "bar");
         final int userId = 0;
-        mCredentialManager.setEnabledProviders(providers, userId, mExecutor, callback);
-        verify(mMockCredentialManagerService).setEnabledProviders(eq(providers), eq(0),
-                callbackCaptor.capture());
+        mCredentialManager.setEnabledProviders(providers, providers, userId, mExecutor, callback);
+        verify(mMockCredentialManagerService)
+                .setEnabledProviders(eq(providers), eq(providers), eq(0), callbackCaptor.capture());
 
         final String errorType = "unknown";
         final String errorMessage = "Unknown error";
@@ -553,15 +597,18 @@
     public void testSetEnabledProviders_success() throws RemoteException {
         OutcomeReceiver<Void, SetEnabledProvidersException> callback = mock(OutcomeReceiver.class);
 
-        ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
-                ISetEnabledProvidersCallback.class);
+        ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor =
+                ArgumentCaptor.forClass(ISetEnabledProvidersCallback.class);
 
         final List<String> providers = List.of("foo", "bar");
+        final List<String> primaryProviders = List.of("foo");
         final int userId = 0;
-        mCredentialManager.setEnabledProviders(providers, userId, mExecutor, callback);
+        mCredentialManager.setEnabledProviders(
+                primaryProviders, providers, userId, mExecutor, callback);
 
-        verify(mMockCredentialManagerService).setEnabledProviders(eq(providers), eq(0),
-                callbackCaptor.capture());
+        verify(mMockCredentialManagerService)
+                .setEnabledProviders(
+                        eq(primaryProviders), eq(providers), eq(0), callbackCaptor.capture());
 
         callbackCaptor.getValue().onResponse();
         verify(callback).onResult(any());
@@ -569,27 +616,29 @@
 
     @Test
     public void testRegisterCredentialDescription_nullRequest() {
-        assertThrows(NullPointerException.class,
+        assertThrows(
+                NullPointerException.class,
                 () -> mCredentialManager.registerCredentialDescription(null));
     }
 
     @Test
     public void testRegisterCredentialDescription_success() throws RemoteException {
         mCredentialManager.registerCredentialDescription(mRegisterRequest);
-        verify(mMockCredentialManagerService).registerCredentialDescription(same(mRegisterRequest),
-                eq(mPackageName));
+        verify(mMockCredentialManagerService)
+                .registerCredentialDescription(same(mRegisterRequest), eq(mPackageName));
     }
 
     @Test
     public void testUnregisterCredentialDescription_nullRequest() {
-        assertThrows(NullPointerException.class,
+        assertThrows(
+                NullPointerException.class,
                 () -> mCredentialManager.unregisterCredentialDescription(null));
     }
 
     @Test
     public void testUnregisterCredentialDescription_success() throws RemoteException {
         mCredentialManager.unregisterCredentialDescription(mUnregisterRequest);
-        verify(mMockCredentialManagerService).unregisterCredentialDescription(
-                same(mUnregisterRequest), eq(mPackageName));
+        verify(mMockCredentialManagerService)
+                .unregisterCredentialDescription(same(mUnregisterRequest), eq(mPackageName));
     }
 }
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
index 76f4171..5939c06 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -119,6 +119,86 @@
         assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
     }
 
+    @Test
+    public void textWithOffsetMapping_blockBeforeTextChanged_deletion() {
+        final String text = "abcdef";
+        final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
+        final CharSequence transformedText =
+                new TestOffsetMapping(spannable, 5, "\n\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        // delete "cd", original text becomes "abef"
+        spannable.delete(2, 4);
+        assertThat(transformedText.toString()).isEqualTo("abe\n\nf");
+        assertLineRange(layout, /* lineBreaks */ 0, 4, 5, 6);
+
+        // delete "abe", original text becomes "f"
+        spannable.delete(0, 3);
+        assertThat(transformedText.toString()).isEqualTo("\n\nf");
+        assertLineRange(layout, /* lineBreaks */ 0, 1, 2, 3);
+    }
+
+    @Test
+    public void textWithOffsetMapping_blockBeforeTextChanged_insertion() {
+        final String text = "abcdef";
+        final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
+        final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        spannable.insert(3, "x");
+        assertThat(transformedText.toString()).isEqualTo("abcx\n\ndef");
+        assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 9);
+
+        spannable.insert(5, "x");
+        assertThat(transformedText.toString()).isEqualTo("abcx\n\ndxef");
+        assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 10);
+    }
+
+    @Test
+    public void textWithOffsetMapping_blockBeforeTextChanged_replace() {
+        final String text = "abcdef";
+        final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
+        final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+        final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        spannable.replace(2, 4, "xx");
+        assertThat(transformedText.toString()).isEqualTo("abxx\n\nef");
+        assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
+    }
+
+    @Test
+    public void textWithOffsetMapping_onlyCallOnTextChanged_notCrash() {
+        String text = "abcdef";
+        SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+        CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+        DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(false)
+                .setDisplayText(transformedText)
+                .build();
+
+        TextWatcher[] textWatcher = spannable.getSpans(0, spannable.length(), TextWatcher.class);
+        assertThat(textWatcher.length).isEqualTo(1);
+
+        textWatcher[0].onTextChanged(spannable, 0, 2, 2);
+    }
+
     private void assertLineRange(Layout layout, int... lineBreaks) {
         final int lineCount = lineBreaks.length - 1;
         assertThat(layout.getLineCount()).isEqualTo(lineCount);
@@ -129,6 +209,50 @@
     }
 
     /**
+     * A test SpannableStringBuilder that doesn't call beforeTextChanged. It's used to test
+     * DynamicLayout against some special cases where beforeTextChanged callback is not properly
+     * called.
+     */
+    private static class TestNoBeforeTextChangeSpannableString extends SpannableStringBuilder {
+
+        TestNoBeforeTextChangeSpannableString(CharSequence text) {
+            super(text);
+        }
+
+        @Override
+        public void setSpan(Object what, int start, int end, int flags) {
+            if (what instanceof TextWatcher) {
+                super.setSpan(new TestNoBeforeTextChangeWatcherWrapper((TextWatcher) what), start,
+                        end, flags);
+            } else {
+                super.setSpan(what, start, end, flags);
+            }
+        }
+    }
+
+    /** A TextWatcherWrapper that blocks beforeTextChanged callback. */
+    private static class TestNoBeforeTextChangeWatcherWrapper implements TextWatcher {
+        private final TextWatcher mTextWatcher;
+
+        TestNoBeforeTextChangeWatcherWrapper(TextWatcher textWatcher) {
+            mTextWatcher = textWatcher;
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            mTextWatcher.onTextChanged(s, start, before, count);
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            mTextWatcher.afterTextChanged(s);
+        }
+    }
+
+    /**
      * A test TransformedText that inserts some text at the given offset.
      */
     private static class TestOffsetMapping implements OffsetMapping, CharSequence {
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 7706d9a..9ef137b 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -224,6 +224,12 @@
         assertThat(spans0to2.length).isEqualTo(1);
         assertThat(spans0to2[0]).isEqualTo(span1);
 
+        // only span2 is in the range of [3, 4).
+        // note: span1 [0, 3) is not in the range because [3, 4) is not collapsed.
+        final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class);
+        assertThat(spans3to4.length).isEqualTo(1);
+        assertThat(spans3to4[0]).isEqualTo(span2);
+
         // span1 and span2 are in the range of [1, 6).
         final TestSpan[] spans1to6 = transformedText.getSpans(1, 6, TestSpan.class);
         assertThat(spans1to6.length).isEqualTo(2);
@@ -262,7 +268,7 @@
         text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
-        // In the transformedText, the new ranges of the spans are:
+        // In the transformedText "abc\uFFFD def", the new ranges of the spans are:
         // span1: [0, 3)
         // span2: [2, 5)
         // span3: [5, 6)
@@ -277,6 +283,12 @@
         assertThat(spans0to2.length).isEqualTo(1);
         assertThat(spans0to2[0]).isEqualTo(span1);
 
+        // only span2 is in the range of [3, 4).
+        // note: span1 [0, 3) is not in the range because [3, 4) is not collapsed.
+        final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class);
+        assertThat(spans3to4.length).isEqualTo(1);
+        assertThat(spans3to4[0]).isEqualTo(span2);
+
         // span1 and span2 are in the range of [1, 5).
         final TestSpan[] spans1to4 = transformedText.getSpans(1, 4, TestSpan.class);
         assertThat(spans1to4.length).isEqualTo(2);
@@ -318,20 +330,143 @@
     }
 
     @Test
-    public void transformedText_getSpanStartAndEnd() {
+    public void transformedText_getSpans_collapsedRange() {
         final SpannableString text = new SpannableString(TEXT);
         final TestSpan span1 = new TestSpan();
         final TestSpan span2 = new TestSpan();
         final TestSpan span3 = new TestSpan();
 
         text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span3, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText "abc\n\n def", the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [3, 3)
+        // span3: [5, 6)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, false, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        // only span1 is in the range of [0, 0).
+        final TestSpan[] spans0to0 = transformedText.getSpans(0, 0, TestSpan.class);
+        assertThat(spans0to0.length).isEqualTo(1);
+        assertThat(spans0to0[0]).isEqualTo(span1);
+
+        // span1 and span 2 are in the range of [3, 3).
+        final TestSpan[] spans3to3 = transformedText.getSpans(3, 3, TestSpan.class);
+        assertThat(spans3to3.length).isEqualTo(2);
+        assertThat(spans3to3[0]).isEqualTo(span1);
+        assertThat(spans3to3[1]).isEqualTo(span2);
+
+        // only the span2 with collapsed range is in the range of [3, 4).
+        final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class);
+        assertThat(spans3to4.length).isEqualTo(1);
+        assertThat(spans3to4[0]).isEqualTo(span2);
+
+        // no span is in the range of [4, 5). (span2 is not mistakenly included.)
+        final TestSpan[] spans4to5 = transformedText.getSpans(4, 5, TestSpan.class);
+        assertThat(spans4to5).isEmpty();
+
+        // only span3 is in the range of [4, 6). (span2 is not mistakenly included.)
+        final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class);
+        assertThat(spans4to6.length).isEqualTo(1);
+        assertThat(spans4to6[0]).isEqualTo(span3);
+
+        // no span is in the range of [4, 4).
+        final TestSpan[] spans4to4 = transformedText.getSpans(4, 4, TestSpan.class);
+        assertThat(spans4to4.length).isEqualTo(0);
+
+        // span3 is in the range of [5, 5).
+        final TestSpan[] spans5to5 = transformedText.getSpans(5, 5, TestSpan.class);
+        assertThat(spans5to5.length).isEqualTo(1);
+        assertThat(spans5to5[0]).isEqualTo(span3);
+
+        // span3 is in the range of [6, 6).
+        final TestSpan[] spans6to6 = transformedText.getSpans(6, 6, TestSpan.class);
+        assertThat(spans6to6.length).isEqualTo(1);
+        assertThat(spans6to6[0]).isEqualTo(span3);
+    }
+
+    @Test
+    public void transformedText_getSpans_collapsedRange_singleLine() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span3, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText "abc\uFFFD def", the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [3, 3)
+        // span3: [4, 5)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, true, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        // only span1 is in the range of [0, 0).
+        final TestSpan[] spans0to0 = transformedText.getSpans(0, 0, TestSpan.class);
+        assertThat(spans0to0.length).isEqualTo(1);
+        assertThat(spans0to0[0]).isEqualTo(span1);
+
+        // span1 and span2 are in the range of [3, 3).
+        final TestSpan[] spans3to3 = transformedText.getSpans(3, 3, TestSpan.class);
+        assertThat(spans3to3.length).isEqualTo(2);
+        assertThat(spans3to3[0]).isEqualTo(span1);
+        assertThat(spans3to3[1]).isEqualTo(span2);
+
+        // only the span2 with collapsed range is in the range of [3, 4).
+        final TestSpan[] spans3to4 = transformedText.getSpans(3, 4, TestSpan.class);
+        assertThat(spans3to4.length).isEqualTo(1);
+        assertThat(spans3to4[0]).isEqualTo(span2);
+
+        // span3 is in the range of [4, 5). (span2 is not mistakenly included.)
+        final TestSpan[] spans4to5 = transformedText.getSpans(4, 5, TestSpan.class);
+        assertThat(spans4to5.length).isEqualTo(1);
+        assertThat(spans4to5[0]).isEqualTo(span3);
+
+        // only span3 is in the range of [4, 6). (span2 is not mistakenly included.)
+        final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class);
+        assertThat(spans4to6.length).isEqualTo(1);
+        assertThat(spans4to6[0]).isEqualTo(span3);
+
+        // span3 is in the range of [4, 4).
+        final TestSpan[] spans4to4 = transformedText.getSpans(4, 4, TestSpan.class);
+        assertThat(spans4to4.length).isEqualTo(1);
+        assertThat(spans4to4[0]).isEqualTo(span3);
+
+        // span3 is in the range of [5, 5).
+        final TestSpan[] spans5to5 = transformedText.getSpans(5, 5, TestSpan.class);
+        assertThat(spans5to5.length).isEqualTo(1);
+        assertThat(spans5to5[0]).isEqualTo(span3);
+    }
+
+    @Test
+    public void transformedText_getSpanStartAndEnd() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+        final TestSpan span4 = new TestSpan();
+        final TestSpan span5 = new TestSpan();
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span4, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span5, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
         // In the transformedText, the new ranges of the spans are:
         // span1: [0, 3)
         // span2: [2, 6)
         // span3: [6, 7)
+        // span4: [3, 3)
+        // span5: [5, 6)
         final InsertModeTransformationMethod transformationMethod =
                 new InsertModeTransformationMethod(3, false, null);
         final Spanned transformedText =
@@ -345,6 +480,12 @@
 
         assertThat(transformedText.getSpanStart(span3)).isEqualTo(6);
         assertThat(transformedText.getSpanEnd(span3)).isEqualTo(7);
+
+        assertThat(transformedText.getSpanStart(span4)).isEqualTo(3);
+        assertThat(transformedText.getSpanEnd(span4)).isEqualTo(3);
+
+        assertThat(transformedText.getSpanStart(span5)).isEqualTo(5);
+        assertThat(transformedText.getSpanEnd(span5)).isEqualTo(6);
     }
 
     @Test
@@ -353,15 +494,21 @@
         final TestSpan span1 = new TestSpan();
         final TestSpan span2 = new TestSpan();
         final TestSpan span3 = new TestSpan();
+        final TestSpan span4 = new TestSpan();
+        final TestSpan span5 = new TestSpan();
 
         text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span4, 3, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span5, 3, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
         // In the transformedText, the new ranges of the spans are:
         // span1: [0, 3)
         // span2: [2, 5)
         // span3: [5, 6)
+        // span4: [3. 3)
+        // span5: [4, 5)
         final InsertModeTransformationMethod transformationMethod =
                 new InsertModeTransformationMethod(3, true, null);
         final Spanned transformedText =
@@ -376,6 +523,12 @@
         assertThat(transformedText.getSpanStart(span3)).isEqualTo(5);
         assertThat(transformedText.getSpanEnd(span3)).isEqualTo(6);
 
+        assertThat(transformedText.getSpanStart(span4)).isEqualTo(3);
+        assertThat(transformedText.getSpanEnd(span4)).isEqualTo(3);
+
+        assertThat(transformedText.getSpanStart(span5)).isEqualTo(4);
+        assertThat(transformedText.getSpanEnd(span5)).isEqualTo(5);
+
         final ReplacementSpan[] replacementSpans =
                 transformedText.getSpans(0, 8, ReplacementSpan.class);
         assertThat(transformedText.getSpanStart(replacementSpans[0])).isEqualTo(3);
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 31c5a76..963014e 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -24,6 +24,10 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
@@ -33,6 +37,8 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Looper;
@@ -58,6 +64,7 @@
 import java.util.Arrays;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
 
 /**
  * Tests for RemoteViews.
@@ -703,4 +710,61 @@
             return null;
         }
     }
+
+    @Test
+    public void visitUris() {
+        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+        final Uri imageUri = Uri.parse("content://media/image");
+        final Icon icon1 = Icon.createWithContentUri("content://media/icon1");
+        final Icon icon2 = Icon.createWithContentUri("content://media/icon2");
+        final Icon icon3 = Icon.createWithContentUri("content://media/icon3");
+        final Icon icon4 = Icon.createWithContentUri("content://media/icon4");
+        views.setImageViewUri(R.id.image, imageUri);
+        views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4);
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        views.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(imageUri));
+        verify(visitor, times(1)).accept(eq(icon1.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4.getUri()));
+    }
+
+    @Test
+    public void visitUris_separateOrientation() {
+        final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final Uri imageUriL = Uri.parse("content://landscape/image");
+        final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1");
+        final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2");
+        final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3");
+        final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4");
+        landscape.setImageViewUri(R.id.image, imageUriL);
+        landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L);
+
+        final RemoteViews portrait = new RemoteViews(mPackage, 33);
+        final Uri imageUriP = Uri.parse("content://portrait/image");
+        final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1");
+        final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2");
+        final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3");
+        final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4");
+        portrait.setImageViewUri(R.id.image, imageUriP);
+        portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P);
+
+        RemoteViews views = new RemoteViews(landscape, portrait);
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        views.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(imageUriL));
+        verify(visitor, times(1)).accept(eq(icon1L.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2L.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3L.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4L.getUri()));
+        verify(visitor, times(1)).accept(eq(imageUriP));
+        verify(visitor, times(1)).accept(eq(icon1P.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2P.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3P.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4P.getUri()));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index 645324d..584ad20 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -28,12 +28,12 @@
 import android.provider.DeviceConfig;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.LatencyTracker.ActionProperties;
 
 import com.google.common.truth.Expect;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -48,7 +48,6 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class LatencyTrackerTest {
     private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
@@ -65,6 +64,11 @@
         mLatencyTracker = FakeLatencyTracker.create();
     }
 
+    @After
+    public void tearDown() {
+        mLatencyTracker.stopListeningForLatencyTrackerConfigChanges();
+    }
+
     @Test
     public void testCujsMapToEnumsCorrectly() {
         List<Field> actions = getAllActionFields();
diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
index 61e976b..76e69bf 100644
--- a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
+++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
@@ -25,8 +25,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.GuardedBy;
 
 import com.google.common.collect.ImmutableMap;
@@ -51,15 +49,17 @@
     private final List<String> mPerfettoTraceNamesTriggered;
     private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate =
             new AtomicReference<>();
-    @Nullable
-    @GuardedBy("mLock")
-    private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null;
+    private final AtomicReference<Callable<Boolean>> mShouldClosePropertiesUpdatedCallable =
+            new AtomicReference<>();
     private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
 
     public static FakeLatencyTracker create() throws Exception {
         Log.i(TAG, "create");
         disableForAllActions();
+        Log.i(TAG, "done disabling all actions");
         FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker();
+        Log.i(TAG, "done creating tracker object");
+        fakeLatencyTracker.startListeningForLatencyTrackerConfigChanges();
         // always return the fake in the disabled state and let the client control the desired state
         fakeLatencyTracker.waitForGlobalEnabledState(false);
         fakeLatencyTracker.waitForAllPropertiesEnableState(false);
@@ -131,27 +131,25 @@
     @Override
     public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
         Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
+
         mLastPropertiesUpdate.set(actionProperties);
-        synchronized (mLock) {
-            if (mShouldClosePropertiesUpdatedCallable != null) {
-                try {
-                    boolean shouldClosePropertiesUpdated =
-                            mShouldClosePropertiesUpdatedCallable.call();
-                    Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result="
-                            + shouldClosePropertiesUpdated);
-                    if (shouldClosePropertiesUpdated) {
-                        Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition");
-                        mShouldClosePropertiesUpdatedCallable = null;
-                        mDeviceConfigPropertiesUpdated.open();
-                    }
-                } catch (Exception e) {
-                    Log.e(TAG, "exception when calling callable", e);
-                    throw new RuntimeException(e);
+        Callable<Boolean> shouldClosePropertiesUpdated =
+                mShouldClosePropertiesUpdatedCallable.get();
+        if (shouldClosePropertiesUpdated != null) {
+            try {
+                boolean result = shouldClosePropertiesUpdated.call();
+                Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" + result);
+                if (result) {
+                    mShouldClosePropertiesUpdatedCallable.set(null);
+                    mDeviceConfigPropertiesUpdated.open();
                 }
-            } else {
-                Log.i(TAG, "no conditional callable set, opening condition");
-                mDeviceConfigPropertiesUpdated.open();
+            } catch (Exception e) {
+                Log.e(TAG, "exception when calling callable", e);
+                throw new RuntimeException(e);
             }
+        } else {
+            Log.i(TAG, "no conditional callable set, opening condition");
+            mDeviceConfigPropertiesUpdated.open();
         }
     }
 
@@ -175,107 +173,82 @@
 
     public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception {
         Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                Log.i(TAG, "verifying if last properties update has all properties enable="
-                        + enabledState);
-                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
-                if (newProperties != null) {
-                    for (int i = 0; i < newProperties.size(); i++) {
-                        if (newProperties.get(i).isEnabled() != enabledState) {
-                            return false;
-                        }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            Log.i(TAG, "verifying if last properties update has all properties enable="
+                    + enabledState);
+            SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+            if (newProperties != null) {
+                for (int i = 0; i < newProperties.size(); i++) {
+                    if (newProperties.get(i).isEnabled() != enabledState) {
+                        return false;
                     }
                 }
-                return true;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
             }
-        }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+            return true;
+        });
     }
 
     public void waitForMatchingActionProperties(ActionProperties actionProperties)
             throws Exception {
         Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                Log.i(TAG, "verifying if last properties update contains matching property ="
-                        + actionProperties);
-                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
-                if (newProperties != null) {
-                    if (newProperties.size() > 0) {
-                        return newProperties.get(actionProperties.getAction()).equals(
-                                actionProperties);
-                    }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            Log.i(TAG, "verifying if last properties update contains matching property ="
+                    + actionProperties);
+            SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+            if (newProperties != null) {
+                if (newProperties.size() > 0) {
+                    return newProperties.get(actionProperties.getAction()).equals(
+                            actionProperties);
                 }
-                return false;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
             }
-        }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+            return false;
+        });
     }
 
     public void waitForActionEnabledState(int action, boolean enabledState) throws Exception {
         Log.i(TAG, "waitForActionEnabledState:"
                 + " action=" + action + ", enabledState=" + enabledState);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                Log.i(TAG, "verifying if last properties update contains action=" + action
-                        + ", enabledState=" + enabledState);
-                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
-                if (newProperties != null) {
-                    if (newProperties.size() > 0) {
-                        return newProperties.get(action).isEnabled() == enabledState;
-                    }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            Log.i(TAG, "verifying if last properties update contains action=" + action
+                    + ", enabledState=" + enabledState);
+            SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+            if (newProperties != null) {
+                if (newProperties.size() > 0) {
+                    return newProperties.get(action).isEnabled() == enabledState;
                 }
-                return false;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
             }
-        }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+            return false;
+        });
     }
 
     public void waitForGlobalEnabledState(boolean enabledState) throws Exception {
         Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                //noinspection deprecation
-                return isEnabled() == enabledState;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
-            }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            //noinspection deprecation
+            return isEnabled() == enabledState;
+        });
+    }
+
+    public void waitForPropertiesCondition(Callable<Boolean> shouldClosePropertiesUpdatedCallable)
+            throws Exception {
+        mShouldClosePropertiesUpdatedCallable.set(shouldClosePropertiesUpdatedCallable);
+        mDeviceConfigPropertiesUpdated.close();
+        if (!shouldClosePropertiesUpdatedCallable.call()) {
+            Log.i(TAG, "waiting for mDeviceConfigPropertiesUpdated condition");
+            mDeviceConfigPropertiesUpdated.block();
         }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+        Log.i(TAG, "waitForPropertiesCondition: returning");
     }
 }
diff --git a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
index 6e01101..c82a70c 100644
--- a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
+++ b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
@@ -37,6 +37,9 @@
     required fixed64 magic_number = 1;
     repeated Transition transitions = 2;
     repeated HandlerMapping handlerMappings = 3;
+    /* offset between real-time clock and elapsed time clock in nanoseconds.
+    Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
+    optional fixed64 real_to_elapsed_time_offset_nanos = 4;
 }
 
 message Transition {
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 54a8f33..3386148 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -67,4 +67,6 @@
     <color name="desktop_mode_caption_menu_text_color">#191C1D</color>
     <color name="desktop_mode_caption_menu_buttons_color_inactive">#191C1D</color>
     <color name="desktop_mode_caption_menu_buttons_color_active">#00677E</color>
+    <color name="desktop_mode_resize_veil_light">#EFF1F2</color>
+    <color name="desktop_mode_resize_veil_dark">#1C1C17</color>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 19eff0e..1793a3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -255,7 +255,7 @@
     private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
-                mTransitionAnimation);
+                mTransitionAnimation, false);
         return a != null && a.getShowBackdrop();
     }
 }
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 24fd86b..e396ba1 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
@@ -300,7 +300,8 @@
                 getShortcutId(),
                 getIcon(),
                 getUser().getIdentifier(),
-                getPackageName());
+                getPackageName(),
+                getTitle());
     }
 
     @Override
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 ba02faf..adc0c9c 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
@@ -215,9 +215,6 @@
             ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
                     0 /* enterResId */, 0 /* exitResId */);
 
-            Rect launchBounds = new Rect();
-            mTaskView.getBoundsOnScreen(launchBounds);
-
             // TODO: I notice inconsistencies in lifecycle
             // Post to keep the lifecycle normal
             post(() -> {
@@ -226,6 +223,9 @@
                             + getBubbleKey());
                 }
                 try {
+                    Rect launchBounds = new Rect();
+                    mTaskView.getBoundsOnScreen(launchBounds);
+
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
                     options.setPendingIntentBackgroundActivityStartMode(
@@ -479,7 +479,7 @@
     void applyThemeAttrs() {
         final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
                 android.R.attr.dialogCornerRadius,
-                android.R.attr.colorBackgroundFloating});
+                com.android.internal.R.attr.materialColorSurfaceBright});
         boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                 mContext.getResources());
         mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index d27d05b..baa23e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -30,7 +30,6 @@
  */
 public class BubbleInfo implements Parcelable {
 
-    // TODO(b/269672147): needs a title string for a11y & that comes from notification
     // TODO(b/269671451): needs whether the bubble is an 'important person' or not
 
     private String mKey; // Same key as the Notification
@@ -46,24 +45,28 @@
      */
     @Nullable
     private Icon mIcon;
+    @Nullable
+    private String mTitle;
 
     public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon,
-            int userId, String packageName) {
+            int userId, String packageName, @Nullable String title) {
         mKey = key;
         mFlags = flags;
         mShortcutId = shortcutId;
         mIcon = icon;
         mUserId = userId;
         mPackageName = packageName;
+        mTitle = title;
     }
 
-    public BubbleInfo(Parcel source) {
+    private BubbleInfo(Parcel source) {
         mKey = source.readString();
         mFlags = source.readInt();
         mShortcutId = source.readString();
         mIcon = source.readTypedObject(Icon.CREATOR);
         mUserId = source.readInt();
         mPackageName = source.readString();
+        mTitle = source.readString();
     }
 
     public String getKey() {
@@ -92,6 +95,11 @@
         return mPackageName;
     }
 
+    @Nullable
+    public String getTitle() {
+        return mTitle;
+    }
+
     /**
      * Whether this bubble is currently being hidden from the stack.
      */
@@ -141,11 +149,12 @@
         parcel.writeTypedObject(mIcon, flags);
         parcel.writeInt(mUserId);
         parcel.writeString(mPackageName);
+        parcel.writeString(mTitle);
     }
 
     @NonNull
     public static final Creator<BubbleInfo> CREATOR =
-            new Creator<BubbleInfo>() {
+            new Creator<>() {
                 public BubbleInfo createFromParcel(Parcel source) {
                     return new BubbleInfo(source);
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index e7dede7..2832c55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -412,7 +412,10 @@
 
     /** Releases and re-inflates {@link DividerView} on the root surface. */
     public void update(SurfaceControl.Transaction t) {
-        if (!mInitialized) return;
+        if (!mInitialized) {
+            init();
+            return;
+        }
         mSplitWindowManager.release(t);
         mImePositionProcessor.reset();
         mSplitWindowManager.init(this, mInsetsState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 838e37a..2bbd870 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -47,6 +47,8 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import dagger.Lazy;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -55,8 +57,6 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-import dagger.Lazy;
-
 /**
  * Controller to show/update compat UI components on Tasks based on whether the foreground
  * activities are in compatibility mode.
@@ -284,13 +284,18 @@
             ShellTaskOrganizer.TaskListener taskListener) {
         CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
         if (layout != null) {
-            // UI already exists, update the UI layout.
-            if (!layout.updateCompatInfo(taskInfo, taskListener,
-                    showOnDisplay(layout.getDisplayId()))) {
-                // The layout is no longer eligible to be shown, remove from active layouts.
+            if (layout.needsToBeRecreated(taskInfo, taskListener)) {
                 mActiveCompatLayouts.remove(taskInfo.taskId);
+                layout.release();
+            } else {
+                // UI already exists, update the UI layout.
+                if (!layout.updateCompatInfo(taskInfo, taskListener,
+                        showOnDisplay(layout.getDisplayId()))) {
+                    // The layout is no longer eligible to be shown, remove from active layouts.
+                    mActiveCompatLayouts.remove(taskInfo.taskId);
+                }
+                return;
             }
-            return;
         }
 
         // Create a new UI layout.
@@ -433,13 +438,18 @@
     private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
             ShellTaskOrganizer.TaskListener taskListener) {
         if (mActiveReachabilityEduLayout != null) {
-            // UI already exists, update the UI layout.
-            if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
-                    showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
-                // The layout is no longer eligible to be shown, remove from active layouts.
+            if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+                mActiveReachabilityEduLayout.release();
                 mActiveReachabilityEduLayout = null;
+            } else {
+                // UI already exists, update the UI layout.
+                if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
+                        showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
+                    // The layout is no longer eligible to be shown, remove from active layouts.
+                    mActiveReachabilityEduLayout = null;
+                }
+                return;
             }
-            return;
         }
         // Create a new UI layout.
         final Context context = getOrCreateDisplayContext(taskInfo.displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 6592292..d4778fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,6 @@
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.app.TaskInfo.CameraCompatControlState;
@@ -53,9 +52,6 @@
 
     private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
 
-    @NonNull
-    private TaskInfo mTaskInfo;
-
     // Remember the last reported states in case visibility changes due to keyguard or IME updates.
     @VisibleForTesting
     boolean mHasSizeCompat;
@@ -77,7 +73,6 @@
             CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
-        mTaskInfo = taskInfo;
         mCallback = callback;
         mHasSizeCompat = taskInfo.topActivityInSizeCompat;
         mCameraCompatControlState = taskInfo.cameraCompatControlState;
@@ -129,7 +124,6 @@
     @Override
     public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
             boolean canShow) {
-        mTaskInfo = taskInfo;
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
         mHasSizeCompat = taskInfo.topActivityInSizeCompat;
@@ -149,7 +143,7 @@
 
     /** Called when the restart button is clicked. */
     void onRestartButtonClicked() {
-        mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener()));
+        mOnRestartButtonClicked.accept(Pair.create(getLastTaskInfo(), getTaskListener()));
     }
 
     /** Called when the camera treatment button is clicked. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 9c4e79c..180498c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -26,6 +26,7 @@
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -65,6 +66,9 @@
     private DisplayLayout mDisplayLayout;
     private final Rect mStableBounds;
 
+    @NonNull
+    private TaskInfo mTaskInfo;
+
     /**
      * Utility class for adding and releasing a View hierarchy for this {@link
      * WindowlessWindowManager} to {@code mLeash}.
@@ -83,6 +87,7 @@
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout) {
         super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */);
+        mTaskInfo = taskInfo;
         mContext = context;
         mSyncQueue = syncQueue;
         mTaskConfig = taskInfo.configuration;
@@ -95,6 +100,17 @@
     }
 
     /**
+     * @return {@code true} if the instance of the specific {@link CompatUIWindowManagerAbstract}
+     * for the current task id needs to be recreated loading the related resources. This happens
+     * if the user switches between Light/Dark mode, if the device is docked/undocked or if the
+     * user switches between multi-window mode to fullscreen where the
+     * {@link ShellTaskOrganizer.TaskListener} implementation is different.
+     */
+    boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+        return hasUiModeChanged(mTaskInfo, taskInfo) || hasTaskListenerChanged(taskListener);
+    }
+
+    /**
      * Returns the z-order of this window which will be passed to the {@link SurfaceControl} once
      * {@link #attachToParentSurface} is called.
      *
@@ -195,6 +211,7 @@
     @VisibleForTesting(visibility = PROTECTED)
     public boolean updateCompatInfo(TaskInfo taskInfo,
             ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
+        mTaskInfo = taskInfo;
         final Configuration prevTaskConfig = mTaskConfig;
         final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
         mTaskConfig = taskInfo.configuration;
@@ -315,6 +332,11 @@
         updateSurfacePosition();
     }
 
+    @Nullable
+    protected TaskInfo getLastTaskInfo() {
+        return mTaskInfo;
+    }
+
     /**
      * Called following a change in the task bounds, display layout stable bounds, or the layout
      * direction.
@@ -402,4 +424,12 @@
     protected final String getTag() {
         return getClass().getSimpleName();
     }
+
+    protected boolean hasTaskListenerChanged(ShellTaskOrganizer.TaskListener newTaskListener) {
+        return !mTaskListener.equals(newTaskListener);
+    }
+
+    protected static boolean hasUiModeChanged(TaskInfo currentTaskInfo, TaskInfo newTaskInfo) {
+        return currentTaskInfo.configuration.uiMode != newTaskInfo.configuration.uiMode;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index 959c50d..9a67258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -19,7 +19,6 @@
 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -69,9 +68,6 @@
     @VisibleForTesting
     LetterboxEduDialogLayout mLayout;
 
-    @NonNull
-    private TaskInfo mTaskInfo;
-
     /**
      * The vertical margin between the dialog container and the task stable bounds (excluding
      * insets).
@@ -99,7 +95,6 @@
             DialogAnimationController<LetterboxEduDialogLayout> animationController,
             DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
-        mTaskInfo = taskInfo;
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
         mAnimationController = animationController;
@@ -197,7 +192,7 @@
         mLayout.setDismissOnClickListener(null);
         mAnimationController.startExitAnimation(mLayout, () -> {
             release();
-            mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+            mOnDismissCallback.accept(Pair.create(getLastTaskInfo(), getTaskListener()));
         });
     }
 
@@ -210,7 +205,6 @@
     @Override
     public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
             boolean canShow) {
-        mTaskInfo = taskInfo;
         mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
 
         return super.updateCompatInfo(taskInfo, taskListener, canShow);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index a18ab91..95bb1fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -20,7 +20,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -52,9 +51,6 @@
 
     private final ShellExecutor mMainExecutor;
 
-    @NonNull
-    private TaskInfo mTaskInfo;
-
     private boolean mIsActivityLetterboxed;
 
     private int mLetterboxVerticalPosition;
@@ -86,7 +82,6 @@
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
             CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
-        mTaskInfo = taskInfo;
         mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
         mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
         mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
@@ -136,7 +131,6 @@
     @Override
     public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
             boolean canShow) {
-        mTaskInfo = taskInfo;
         final boolean prevIsActivityLetterboxed = mIsActivityLetterboxed;
         final int prevLetterboxVerticalPosition = mLetterboxVerticalPosition;
         final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition;
@@ -222,14 +216,14 @@
         if (mLayout == null) {
             return;
         }
-
+        final TaskInfo lastTaskInfo = getLastTaskInfo();
         final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
-                || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(mTaskInfo)
+                || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo)
                 || (mHasUserDoubleTapped
                     && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
                         || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
         final boolean eligibleForDisplayVerticalEducation = mForceUpdate
-                || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(mTaskInfo)
+                || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo)
                 || (mHasUserDoubleTapped
                     && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
                         || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
@@ -241,7 +235,7 @@
             mLayout.handleVisibility(eligibleForDisplayHorizontalEducation,
                     eligibleForDisplayVerticalEducation,
                     mLetterboxVerticalPosition, mLetterboxHorizontalPosition, availableWidth,
-                    availableHeight, mCompatUIConfiguration, mTaskInfo);
+                    availableHeight, mCompatUIConfiguration, lastTaskInfo);
             if (!mHasLetterboxSizeChanged) {
                 updateHideTime();
                 mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
index 51e5141..a770da2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
@@ -19,7 +19,6 @@
 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -67,9 +66,6 @@
      */
     private final int mDialogVerticalMargin;
 
-    @NonNull
-    private TaskInfo mTaskInfo;
-
     @Nullable
     @VisibleForTesting
     RestartDialogLayout mLayout;
@@ -95,7 +91,6 @@
             DialogAnimationController<RestartDialogLayout> animationController,
             CompatUIConfiguration compatUIConfiguration) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
-        mTaskInfo = taskInfo;
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
         mOnRestartCallback = onRestartCallback;
@@ -125,7 +120,7 @@
     protected boolean eligibleToShowLayout() {
         // We don't show this dialog if the user has explicitly selected so clicking on a checkbox.
         return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null
-                || mCompatUIConfiguration.shouldShowRestartDialogAgain(mTaskInfo));
+                || mCompatUIConfiguration.shouldShowRestartDialogAgain(getLastTaskInfo()));
     }
 
     @Override
@@ -143,18 +138,6 @@
         mRequestRestartDialog = enabled;
     }
 
-    @Override
-    public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
-            boolean canShow) {
-        mTaskInfo = taskInfo;
-        return super.updateCompatInfo(taskInfo, taskListener, canShow);
-    }
-
-    boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
-        return taskInfo.configuration.uiMode != mTaskInfo.configuration.uiMode
-                || !getTaskListener().equals(taskListener);
-    }
-
     private void updateDialogMargins() {
         if (mLayout == null) {
             return;
@@ -191,6 +174,7 @@
             // Dialog has already been released.
             return;
         }
+        final TaskInfo lastTaskInfo = getLastTaskInfo();
         mLayout.setDismissOnClickListener(this::onDismiss);
         mLayout.setRestartOnClickListener(dontShowAgain -> {
             if (mLayout != null) {
@@ -200,9 +184,9 @@
                 });
             }
             if (dontShowAgain) {
-                mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo);
+                mCompatUIConfiguration.setDontShowRestartDialogAgain(lastTaskInfo);
             }
-            mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+            mOnRestartCallback.accept(Pair.create(lastTaskInfo, getTaskListener()));
         });
         // Focus on the dialog title for accessibility.
         mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
@@ -216,7 +200,7 @@
         mLayout.setDismissOnClickListener(null);
         mAnimationController.startExitAnimation(mLayout, () -> {
             release();
-            mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+            mOnDismissCallback.accept(Pair.create(getLastTaskInfo(), getTaskListener()));
         });
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 00cc57f..3ab175d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -23,6 +23,8 @@
 import androidx.core.util.forEach
 import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.util.KtProtoLog
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 
@@ -140,6 +142,12 @@
 
         val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
         if (added) {
+            KtProtoLog.d(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTaskRepo: add active task=%d displayId=%d",
+                taskId,
+                displayId
+            )
             activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
         }
         return added
@@ -158,6 +166,9 @@
                 result = true
             }
         }
+        if (result) {
+            KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
+        }
         return result
     }
 
@@ -221,6 +232,17 @@
             displayData[displayId]?.visibleTasks?.remove(taskId)
         }
         val newCount = getVisibleTaskCount(displayId)
+
+        if (prevCount != newCount) {
+            KtProtoLog.d(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
+                taskId,
+                visible,
+                displayId
+            )
+        }
+
         // Check if count changed and if there was no tasks or this is the first task
         if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
             notifyVisibleTaskListeners(displayId, newCount > 0)
@@ -244,6 +266,11 @@
      * Add (or move if it already exists) the task to the top of the ordered list.
      */
     fun addOrMoveFreeformTaskToTop(taskId: Int) {
+        KtProtoLog.d(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTaskRepo: add or move task to top taskId=%d",
+            taskId
+        )
         if (freeformTasksInZOrder.contains(taskId)) {
             freeformTasksInZOrder.remove(taskId)
         }
@@ -254,6 +281,11 @@
      * Remove the task from the ordered list.
      */
     fun removeFreeformTask(taskId: Int) {
+        KtProtoLog.d(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTaskRepo: remove freeform task from ordered list taskId=%d",
+            taskId
+        )
         freeformTasksInZOrder.remove(taskId)
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b310938..91bb155 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -39,7 +39,6 @@
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
-import com.android.internal.protolog.common.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
@@ -56,6 +55,7 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.sysui.ShellSharedConstants
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 
@@ -91,7 +91,7 @@
     }
 
     private fun onInit() {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+        KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
         shellController.addExternalInterface(
             ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
             { createExternalInterface() },
@@ -102,7 +102,7 @@
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
     fun showDesktopApps(displayId: Int) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
         val wct = WindowContainerTransaction()
         // TODO(b/278084491): pass in display id
         bringDesktopAppsToFront(displayId, wct)
@@ -130,8 +130,11 @@
 
     /** Move a task to desktop */
     fun moveToDesktop(task: RunningTaskInfo) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
-
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveToDesktop taskId=%d",
+            task.taskId
+        )
         val wct = WindowContainerTransaction()
         // Bring other apps to front first
         bringDesktopAppsToFront(task.displayId, wct)
@@ -147,10 +150,12 @@
      * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
      * startBounds
      */
-    fun moveToFreeform(
-            taskInfo: RunningTaskInfo,
-            startBounds: Rect
-    ) {
+    fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+            taskInfo.taskId
+        )
         val wct = WindowContainerTransaction()
         moveHomeTaskToFront(wct)
         addMoveToDesktopChanges(wct, taskInfo.getToken())
@@ -165,10 +170,12 @@
     }
 
     /** Brings apps to front and sets freeform task bounds */
-    private fun moveToDesktopWithAnimation(
-            taskInfo: RunningTaskInfo,
-            freeformBounds: Rect
-    ) {
+    private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveToDesktop with animation taskId=%d",
+            taskInfo.taskId
+        )
         val wct = WindowContainerTransaction()
         bringDesktopAppsToFront(taskInfo.displayId, wct)
         addMoveToDesktopChanges(wct, taskInfo.getToken())
@@ -190,7 +197,11 @@
 
     /** Move a task to fullscreen */
     fun moveToFullscreen(task: RunningTaskInfo) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveToFullscreen taskId=%d",
+            task.taskId
+        )
 
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
@@ -206,6 +217,11 @@
      * status bar area
      */
     fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+        KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: cancelMoveToFreeform taskId=%d",
+                task.taskId
+        )
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -218,6 +234,11 @@
     }
 
     private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) {
+        KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: moveToFullscreen with animation taskId=%d",
+                task.taskId
+        )
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
 
@@ -230,8 +251,14 @@
         }
     }
 
-    /** Move a task to the front **/
+    /** Move a task to the front */
     fun moveTaskToFront(taskInfo: RunningTaskInfo) {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveTaskToFront taskId=%d",
+            taskInfo.taskId
+        )
+
         val wct = WindowContainerTransaction()
         wct.reorder(taskInfo.token, true)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -255,10 +282,10 @@
     fun moveToNextDisplay(taskId: Int) {
         val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
         if (task == null) {
-            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+            KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
             return
         }
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
                 taskId, task.displayId)
 
         val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
@@ -269,7 +296,7 @@
             newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
         }
         if (newDisplayId == null) {
-            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+            KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
             return
         }
         moveToDisplay(task, newDisplayId)
@@ -281,17 +308,17 @@
      * No-op if task is already on that display per [RunningTaskInfo.displayId].
      */
     private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
                 task.taskId, displayId)
 
         if (task.displayId == displayId) {
-            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+            KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
             return
         }
 
         val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
         if (displayAreaInfo == null) {
-            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+            KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
             return
         }
 
@@ -316,7 +343,7 @@
     }
 
     private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront")
         val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId)
 
         // First move home to front and then other tasks on top of it
@@ -397,9 +424,9 @@
         if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
             // If there are any visible desktop tasks, switch the task to freeform
             if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
-                ProtoLog.d(
+                KtProtoLog.d(
                     WM_SHELL_DESKTOP_MODE,
-                    "DesktopTasksController#handleRequest: switch fullscreen task to freeform," +
+                    "DesktopTasksController: switch fullscreen task to freeform on transition" +
                         " taskId=%d",
                     task.taskId
                 )
@@ -414,9 +441,9 @@
             // If no visible desktop tasks, switch this task to freeform as the transition came
             // outside of this controller
             if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
-                ProtoLog.d(
+                KtProtoLog.d(
                     WM_SHELL_DESKTOP_MODE,
-                    "DesktopTasksController#handleRequest: switch freeform task to fullscreen," +
+                    "DesktopTasksController: switch freeform task to fullscreen oon transition" +
                         " taskId=%d",
                     task.taskId
                 )
@@ -627,8 +654,6 @@
         }
     }
 
-
-
     /** The interface for calls from outside the host process. */
     @BinderThread
     private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index ccbb9cf..a3803ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -1,3 +1,4 @@
 # WM shell sub-module freeform owners
 atsjenk@google.com
+jorgegil@google.com
 madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index 24d0b99..f51eb52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -180,6 +180,35 @@
         return null;
     }
 
+
+    /**
+     * Returns the source hint rect if it is valid (if provided and is contained by the current
+     * task bounds, while not smaller than the destination bounds).
+     */
+    @Nullable
+    public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds,
+            Rect destinationBounds) {
+        Rect sourceRectHint = getValidSourceHintRect(params, sourceBounds);
+        if (!isSourceRectHintValidForEnterPip(sourceRectHint, destinationBounds)) {
+            sourceRectHint = null;
+        }
+        return sourceRectHint;
+    }
+
+    /**
+     * This is a situation in which the source rect hint on at least one axis is smaller
+     * than the destination bounds, which represents a problem because we would have to scale
+     * up that axis to fit the bounds. So instead, just fallback to the non-source hint
+     * animation in this case.
+     *
+     * @return {@code false} if the given source is too small to use for the entering animation.
+     */
+    static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+        return sourceRectHint != null
+                && sourceRectHint.width() > destinationBounds.width()
+                && sourceRectHint.height() > destinationBounds.height();
+    }
+
     public float getDefaultAspectRatio() {
         return mDefaultAspectRatio;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6cedcf5..363d675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1657,8 +1657,8 @@
                     "%s: Abort animation, invalid leash", TAG);
             return null;
         }
-        if (isInPipDirection(direction)
-                && !isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) {
+        if (isInPipDirection(direction) && !PipBoundsAlgorithm
+                .isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) {
             // The given source rect hint is too small for enter PiP animation, reset it to null.
             sourceHintRect = null;
         }
@@ -1757,20 +1757,6 @@
     }
 
     /**
-     * This is a situation in which the source rect hint on at least one axis is smaller
-     * than the destination bounds, which represents a problem because we would have to scale
-     * up that axis to fit the bounds. So instead, just fallback to the non-source hint
-     * animation in this case.
-     *
-     * @return {@code false} if the given source is too small to use for the entering animation.
-     */
-    private boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
-        return sourceRectHint != null
-                && sourceRectHint.width() > destinationBounds.width()
-                && sourceRectHint.height() > destinationBounds.height();
-    }
-
-    /**
      * Sync with {@link SplitScreenController} on destination bounds if PiP is going to
      * split screen.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 98db707..046d6fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -470,6 +470,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
         TransitionInfo.Change pipChange = pipTaskChange;
+        SurfaceControl activitySc = null;
         if (mCurrentPipTaskToken == null) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
@@ -482,6 +483,7 @@
                 if (mCurrentPipTaskToken.equals(change.getLastParent())) {
                     // Find the activity that is exiting PiP.
                     pipChange = change;
+                    activitySc = change.getLeash();
                     break;
                 }
             }
@@ -498,17 +500,36 @@
         // case it may not be in the screen coordinate.
         // Reparent the pip leash to the root with max layer so that we can animate it outside of
         // parent crop, and make sure it is not covered by other windows.
-        final SurfaceControl pipLeash = pipChange.getLeash();
-        final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
-        startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
+        final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info);
+        final SurfaceControl pipLeash;
+        if (activitySc != null) {
+            // Use a local leash to animate activity in case the activity has letterbox which may
+            // be broken by PiP animation, e.g. always end at 0,0 in parent and unable to include
+            // letterbox area in crop bounds.
+            final SurfaceControl activitySurface = pipChange.getLeash();
+            pipLeash = new SurfaceControl.Builder()
+                    .setName(activitySc + "_pip-leash")
+                    .setContainerLayer()
+                    .setHidden(false)
+                    .setParent(root.getLeash())
+                    .build();
+            startTransaction.reparent(activitySurface, pipLeash);
+            // Put the activity at local position with offset in case it is letterboxed.
+            final Point activityOffset = pipChange.getEndRelOffset();
+            startTransaction.setPosition(activitySc, activityOffset.x, activityOffset.y);
+        } else {
+            pipLeash = pipChange.getLeash();
+            startTransaction.reparent(pipLeash, root.getLeash());
+        }
         startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
         // Note: because of this, the bounds to animate should be translated to the root coordinate.
-        final Point offset = info.getRoot(rootIdx).getOffset();
+        final Point offset = root.getOffset();
         final Rect currentBounds = mPipBoundsState.getBounds();
         currentBounds.offset(-offset.x, -offset.y);
         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
 
         final WindowContainerToken pipTaskToken = pipChange.getContainer();
+        final boolean useLocalLeash = activitySc != null;
         final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
                 mPipBoundsState.getDisplayBounds());
         mFinishCallback = (wct, wctCB) -> {
@@ -518,6 +539,14 @@
                 wct.setBounds(pipTaskToken, null);
                 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
             }
+            if (useLocalLeash) {
+                if (mPipAnimationController.isAnimating()) {
+                    mPipAnimationController.getCurrentAnimator().end();
+                }
+                // Make sure the animator don't use the released leash, e.g. mergeAnimation.
+                mPipAnimationController.resetAnimatorState();
+                finishTransaction.remove(pipLeash);
+            }
             finishCallback.onTransitionFinished(wct, wctCB);
         };
         mFinishTransaction = finishTransaction;
@@ -545,7 +574,8 @@
         // Set the initial frame as scaling the end to the start.
         final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
         destinationBounds.offset(-offset.x, -offset.y);
-        startTransaction.setWindowCrop(pipLeash, destinationBounds);
+        startTransaction.setWindowCrop(pipLeash, destinationBounds.width(),
+                destinationBounds.height());
         mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds,
                 currentBounds);
         startTransaction.apply();
@@ -764,7 +794,7 @@
         final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
-                taskInfo.pictureInPictureParams, currentBounds);
+                taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
         if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
             // Need to get the bounds of new rotation in old rotation for fixed rotation,
             computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5c2f1438..b8373f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1538,7 +1538,7 @@
     }
 
     void finishEnterSplitScreen(SurfaceControl.Transaction t) {
-        mSplitLayout.init();
+        mSplitLayout.update(t);
         setDividerVisibility(true, t);
         // Ensure divider surface are re-parented back into the hierarchy at the end of the
         // transition. See Transition#buildFinishTransaction for more detail.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 94190c7..d27933e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -453,7 +453,7 @@
             return;
         }
 
-        finishTransaction.reparent(mTaskLeash, null).apply();
+        finishTransaction.reparent(mTaskLeash, null);
 
         if (mListener != null) {
             final int taskId = mTaskInfo.taskId;
@@ -490,13 +490,11 @@
         if (mSurfaceCreated) {
             // Surface is ready, so just reparent the task to this surface control
             startTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .show(mTaskLeash)
-                    .apply();
+                    .show(mTaskLeash);
             // Also reparent on finishTransaction since the finishTransaction will reparent back
             // to its "original" parent by default.
             finishTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .setPosition(mTaskLeash, 0, 0)
-                    .apply();
+                    .setPosition(mTaskLeash, 0, 0);
             mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen());
             mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
             wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 689f9e1..fe2faaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -363,7 +363,8 @@
                     continue;
                 }
                 startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
-                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
+                    .setPosition(chg.getLeash(), 0, 0);
                 changesHandled++;
             }
         }
@@ -377,7 +378,6 @@
         }
         // No animation, just show it immediately.
         startTransaction.apply();
-        finishTransaction.apply();
         finishCallback.onTransitionFinished(wct, null /* wctCB */);
         startNextTransition();
         return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 7d9ab66..6fa1861 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -150,9 +150,9 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitScreenVisible()) {
+        if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
-                    + "Split-Screen is foreground, so treat it as Mixed.");
+                    + "Split-Screen is active, so treat it as Mixed.");
             if (request.getRemoteTransition() != null) {
                 throw new IllegalStateException("Unexpected remote transition in"
                         + "pip-enter-from-split request");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1ee52ae..21dca95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -24,6 +24,7 @@
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -36,12 +37,8 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
@@ -92,7 +89,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
-import android.view.WindowManager.TransitionType;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
@@ -329,6 +325,8 @@
         @ColorInt int backgroundColorForTransition = 0;
         final int wallpaperTransit = getWallpaperTransitType(info);
         boolean isDisplayRotationAnimationStarted = false;
+        final boolean isDreamTransition = isDreamTransition(info);
+
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
@@ -343,9 +341,10 @@
                 continue;
             }
             final boolean isTask = change.getTaskInfo() != null;
+            final int mode = change.getMode();
             boolean isSeamlessDisplayChange = false;
 
-            if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+            if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
                 if (info.getType() == TRANSIT_CHANGE) {
                     final int anim = getRotationAnimationHint(change, info, mDisplayController);
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
@@ -361,7 +360,7 @@
                 }
             }
 
-            if (change.getMode() == TRANSIT_CHANGE) {
+            if (mode == TRANSIT_CHANGE) {
                 // If task is child task, only set position in parent and update crop when needed.
                 if (isTask && change.getParent() != null
                         && info.getChange(change.getParent()).getTaskInfo() != null) {
@@ -410,8 +409,7 @@
 
             // Hide the invisible surface directly without animating it if there is a display
             // rotation animation playing.
-            if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(
-                    change.getMode())) {
+            if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) {
                 startTransaction.hide(change.getLeash());
                 continue;
             }
@@ -424,16 +422,12 @@
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
-            Animation a = loadAnimation(info, change, wallpaperTransit);
+            Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
             if (a != null) {
                 if (isTask) {
-                    final @TransitionType int type = info.getType();
-                    final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN
-                            || type == TRANSIT_CLOSE
-                            || type == TRANSIT_TO_FRONT
-                            || type == TRANSIT_TO_BACK;
                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
-                    if (isOpenOrCloseTransition && !isTranslucent
+                    if (!isTranslucent && TransitionUtil.isOpenOrCloseMode(mode)
+                            && TransitionUtil.isOpenOrCloseMode(info.getType())
                             && wallpaperTransit == WALLPAPER_TRANSITION_NONE) {
                         // Use the overview background as the background for the animation
                         final Context uiContext = ActivityThread.currentActivityThread()
@@ -458,7 +452,7 @@
                         backgroundColorForTransition);
 
                 if (!isTask && a.hasExtension()) {
-                    if (!TransitionUtil.isOpeningType(change.getMode())) {
+                    if (!TransitionUtil.isOpeningType(mode)) {
                         // Can screenshot now (before startTransaction is applied)
                         edgeExtendWindow(change, a, startTransaction, finishTransaction);
                     } else {
@@ -469,7 +463,7 @@
                     }
                 }
 
-                final Rect clipRect = TransitionUtil.isClosingType(change.getMode())
+                final Rect clipRect = TransitionUtil.isClosingType(mode)
                         ? new Rect(mRotator.getEndBoundsInStartRotation(change))
                         : new Rect(change.getEndAbsBounds());
                 clipRect.offsetTo(0, 0);
@@ -519,6 +513,18 @@
         return true;
     }
 
+    private static boolean isDreamTransition(@NonNull TransitionInfo info) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.getTaskInfo() != null
+                    && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -572,7 +578,8 @@
 
     @Nullable
     private Animation loadAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, int wallpaperTransit) {
+            @NonNull TransitionInfo.Change change, int wallpaperTransit,
+            boolean isDreamTransition) {
         Animation a;
 
         final int type = info.getType();
@@ -630,7 +637,8 @@
             // If there's a scene-transition, then jump-cut.
             return null;
         } else {
-            a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
+            a = loadAttributeAnimation(
+                    info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
         }
 
         if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
index 0cede90..e27e4f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
@@ -28,7 +28,6 @@
 import android.util.Log;
 
 import com.android.internal.util.TraceBuffer;
-import com.android.wm.shell.nano.HandlerMapping;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 
 import com.google.protobuf.nano.MessageNano;
@@ -41,6 +40,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Queue;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Helper class to collect and dump transition traces.
@@ -241,6 +241,10 @@
                     new com.android.wm.shell.nano.WmShellTransitionTraceProto();
             proto.magicNumber = MAGIC_NUMBER_VALUE;
             writeHandlerMappingToProto(proto);
+            long timeOffsetNs =
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+                            - SystemClock.elapsedRealtimeNanos();
+            proto.realToElapsedTimeOffsetNanos = timeOffsetNs;
             int pid = android.os.Process.myPid();
             LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
                     + " from process " + pid);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 0f4645c..19d8384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_NONE;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -63,7 +62,7 @@
     @Nullable
     public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, int wallpaperTransit,
-            @NonNull TransitionAnimation transitionAnimation) {
+            @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
         final int type = info.getType();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
@@ -71,11 +70,9 @@
         final boolean isTask = change.getTaskInfo() != null;
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
-        final boolean isDream =
-                isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
         int animAttr = 0;
         boolean translucent = false;
-        if (isDream) {
+        if (isDreamTransition) {
             if (type == TRANSIT_OPEN) {
                 animAttr = enter
                         ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index a73ab77..ab27c55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -19,8 +19,6 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_SLEEP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -503,7 +501,9 @@
      */
     private static void setupAnimHierarchy(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
-        boolean isOpening = isOpeningType(info.getType());
+        final int type = info.getType();
+        final boolean isOpening = isOpeningType(type);
+        final boolean isClosing = isClosingType(type);
         for (int i = 0; i < info.getRootCount(); ++i) {
             t.show(info.getRoot(i).getLeash());
         }
@@ -556,7 +556,13 @@
                     layer = zSplitLine + numChanges - i;
                 }
             } else { // CHANGE or other
-                layer = zSplitLine + numChanges - i;
+                if (isClosing) {
+                    // Put below CLOSE mode.
+                    layer = zSplitLine - i;
+                } else {
+                    // Put above CLOSE mode.
+                    layer = zSplitLine + numChanges - i;
+                }
             }
             t.setLayer(leash, layer);
         }
@@ -851,14 +857,13 @@
                     active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
+                mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
                 return;
             }
         }
         // Otherwise give every other handler a chance
         active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
                 active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
-
-        mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
     }
 
     /**
@@ -877,6 +882,7 @@
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
                         mHandlers.get(i));
+                mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
                 return mHandlers.get(i);
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
new file mode 100644
index 0000000..9b48a54
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.util
+
+import android.util.Log
+import com.android.internal.protolog.common.IProtoLogGroup
+import com.android.wm.shell.protolog.ShellProtoLogImpl
+
+/**
+ * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
+ * logging from Kotlin classes as ProtoLog does not have support for Kotlin.
+ *
+ * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup].
+ */
+// TODO(b/168581922): remove once ProtoLog adds support for Kotlin
+class KtProtoLog {
+    companion object {
+        /** @see [com.android.internal.protolog.common.ProtoLog.d] */
+        fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
+            if (ShellProtoLogImpl.isEnabled(group)) {
+                Log.d(group.tag, String.format(messageString, *args))
+            }
+        }
+
+        /** @see [com.android.internal.protolog.common.ProtoLog.v] */
+        fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
+            if (ShellProtoLogImpl.isEnabled(group)) {
+                Log.v(group.tag, String.format(messageString, *args))
+            }
+        }
+
+        /** @see [com.android.internal.protolog.common.ProtoLog.i] */
+        fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
+            if (ShellProtoLogImpl.isEnabled(group)) {
+                Log.i(group.tag, String.format(messageString, *args))
+            }
+        }
+
+        /** @see [com.android.internal.protolog.common.ProtoLog.w] */
+        fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
+            if (ShellProtoLogImpl.isEnabled(group)) {
+                Log.w(group.tag, String.format(messageString, *args))
+            }
+        }
+
+        /** @see [com.android.internal.protolog.common.ProtoLog.e] */
+        fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
+            if (ShellProtoLogImpl.isEnabled(group)) {
+                Log.e(group.tag, String.format(messageString, *args))
+            }
+        }
+
+        /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
+        fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
+            if (ShellProtoLogImpl.isEnabled(group)) {
+                Log.wtf(group.tag, String.format(messageString, *args))
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 7d05c0e..402b0ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -68,6 +68,12 @@
         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
     }
 
+    /** Returns {@code true} if the transition is opening or closing mode. */
+    public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
+        return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
+                || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
+    }
+
     /** Returns {@code true} if the transition has a display change. */
     public static boolean hasDisplayChange(@NonNull TransitionInfo info) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -311,7 +317,7 @@
 
     private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
             SurfaceControl leash) {
-        return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+        return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
                 leash, false /* isTranslucent */, null /* clipRect */,
                 null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
                 new android.graphics.Point(0, 0) /* position */, change.getStartAbsBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 2bb3cce..39fb793 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -22,7 +22,6 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
 import android.util.SparseArray;
@@ -186,8 +185,9 @@
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
-                windowDecoration, taskInfo);
+        final DragPositioningCallback dragPositioningCallback =
+                new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
+                        null /* disallowedAreaForEndBounds */);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -198,17 +198,6 @@
         setupCaptionColor(taskInfo, windowDecoration);
     }
 
-    private FluidResizeTaskPositioner createDragPositioningCallback(
-            CaptionWindowDecoration windowDecoration, RunningTaskInfo taskInfo) {
-        final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
-        final int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
-                .stableInsets().top;
-        final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
-                statusBarHeight);
-        return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds);
-    }
-
     private class CaptionTouchEventListener implements
             View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 9bcb77f..9f79d21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -21,6 +21,8 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
@@ -42,24 +44,24 @@
     private final Rect mRepositionTaskBounds = new Rect();
     // If a task move (not resize) finishes in this region, the positioner will not attempt to
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds = new Rect();
+    private final Rect mDisallowedAreaForEndBounds;
     private boolean mHasDragResized;
     private int mCtrlType;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, Rect disallowedAreaForEndBounds) {
+            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
         this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
                 dragStartListener -> {}, SurfaceControl.Transaction::new);
     }
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, Rect disallowedAreaForEndBounds,
+            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
             Supplier<SurfaceControl.Transaction> supplier) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
-        mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds);
+        mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index fa2d7a8..4899453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -19,8 +19,10 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.ColorRes;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -109,6 +111,10 @@
             t.reparent(mVeilSurface, parentSurface);
             mParentSurface = parentSurface;
         }
+
+        int backgroundColorId = getBackgroundColorId();
+        mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
+
         t.show(mVeilSurface)
                 .apply();
         final ValueAnimator animator = new ValueAnimator();
@@ -158,6 +164,17 @@
         animator.start();
     }
 
+    @ColorRes
+    private int getBackgroundColorId() {
+        Configuration configuration = mContext.getResources().getConfiguration();
+        if ((configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES) {
+            return R.color.desktop_mode_resize_veil_dark;
+        } else {
+            return R.color.desktop_mode_resize_veil_light;
+        }
+    }
+
     /**
      * Dispose of veil when it is no longer needed, likely on close of its container decor.
      */
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 64dfc3e..deebad5 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -8,3 +8,4 @@
 hwwang@google.com
 chenghsiuchang@google.com
 atsjenk@google.com
+jorgegil@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
new file mode 100644
index 0000000..8fbac1d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles
+
+import android.os.Parcel
+import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleInfoTest : ShellTestCase() {
+
+    @Test
+    fun bubbleInfo() {
+        val bubbleInfo = BubbleInfo("key", 0, "shortcut id", null, 6, "com.some.package", "title")
+        val parcel = Parcel.obtain()
+        bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE)
+        parcel.setDataPosition(0)
+
+        val bubbleInfoFromParcel = BubbleInfo.CREATOR.createFromParcel(parcel)
+
+        assertThat(bubbleInfo.key).isEqualTo(bubbleInfoFromParcel.key)
+        assertThat(bubbleInfo.flags).isEqualTo(bubbleInfoFromParcel.flags)
+        assertThat(bubbleInfo.shortcutId).isEqualTo(bubbleInfoFromParcel.shortcutId)
+        assertThat(bubbleInfo.icon).isEqualTo(bubbleInfoFromParcel.icon)
+        assertThat(bubbleInfo.userId).isEqualTo(bubbleInfoFromParcel.userId)
+        assertThat(bubbleInfo.packageName).isEqualTo(bubbleInfoFromParcel.packageName)
+        assertThat(bubbleInfo.title).isEqualTo(bubbleInfoFromParcel.title)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 4de5298..55781f1b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -37,6 +37,7 @@
 
 import android.app.ActivityManager;
 import android.app.TaskInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
@@ -455,12 +456,21 @@
         verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
     }
 
+    @Test
+    public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+        ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+        newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
+
+        Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+    }
+
     private static TaskInfo createTaskInfo(boolean hasSizeCompat,
             @TaskInfo.CameraCompatControlState int cameraCompatControlState) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
         taskInfo.taskId = TASK_ID;
         taskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.cameraCompatControlState = cameraCompatControlState;
+        taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 5bcc72e..973a99c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -21,6 +21,7 @@
 
 import android.app.ActivityManager;
 import android.app.TaskInfo;
+import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -31,6 +32,8 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,63 +49,61 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class ReachabilityEduWindowManagerTest extends ShellTestCase {
-
-    private static final int USER_ID = 1;
-    private static final int TASK_ID = 1;
-
     @Mock
     private SyncTransactionQueue mSyncTransactionQueue;
     @Mock
     private ShellTaskOrganizer.TaskListener mTaskListener;
     @Mock
-    private CompatUIController.CompatUICallback mCallback;
-    @Mock
     private CompatUIConfiguration mCompatUIConfiguration;
     @Mock
     private DisplayLayout mDisplayLayout;
-
     private TestShellExecutor mExecutor;
+    private TaskInfo mTaskInfo;
+    private ReachabilityEduWindowManager mWindowManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mExecutor = new TestShellExecutor();
+        mTaskInfo = new ActivityManager.RunningTaskInfo();
+        mTaskInfo.configuration.uiMode =
+                (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK)
+                        | Configuration.UI_MODE_NIGHT_NO;
+        mTaskInfo.configuration.uiMode =
+                (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK)
+                        | Configuration.UI_MODE_TYPE_NORMAL;
+        mWindowManager = createReachabilityEduWindowManager(mTaskInfo);
     }
 
     @Test
     public void testCreateLayout_notEligible_doesNotCreateLayout() {
-        final ReachabilityEduWindowManager windowManager = createReachabilityEduWindowManager(
-                createTaskInfo(/* userId= */ USER_ID, /*isLetterboxDoubleTapEnabled  */ false));
+        assertFalse(mWindowManager.createLayout(/* canShow= */ true));
 
-        assertFalse(windowManager.createLayout(/* canShow= */ true));
+        assertNull(mWindowManager.mLayout);
+    }
 
-        assertNull(windowManager.mLayout);
+    @Test
+    public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+        ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+        newTaskInfo.configuration.uiMode =
+                (newTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK)
+                        | Configuration.UI_MODE_TYPE_DESK;
+
+        Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+    }
+
+    @Test
+    public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
+        ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+        mTaskInfo.configuration.uiMode =
+                (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK)
+                        | Configuration.UI_MODE_NIGHT_YES;
+
+        Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
     }
 
     private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
         return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
                 mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
     }
-
-    private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled) {
-        return createTaskInfo(userId, /* isLetterboxDoubleTapEnabled */ isLetterboxDoubleTapEnabled,
-                /* topActivityLetterboxVerticalPosition */ -1,
-                /* topActivityLetterboxHorizontalPosition */ -1,
-                /* topActivityLetterboxWidth */ -1,
-                /* topActivityLetterboxHeight */ -1);
-    }
-
-    private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled,
-            int topActivityLetterboxVerticalPosition, int topActivityLetterboxHorizontalPosition,
-            int topActivityLetterboxWidth, int topActivityLetterboxHeight) {
-        ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
-        taskInfo.userId = userId;
-        taskInfo.taskId = TASK_ID;
-        taskInfo.isLetterboxDoubleTapEnabled = isLetterboxDoubleTapEnabled;
-        taskInfo.topActivityLetterboxVerticalPosition = topActivityLetterboxVerticalPosition;
-        taskInfo.topActivityLetterboxHorizontalPosition = topActivityLetterboxHorizontalPosition;
-        taskInfo.topActivityLetterboxWidth = topActivityLetterboxWidth;
-        taskInfo.topActivityLetterboxHeight = topActivityLetterboxHeight;
-        return taskInfo;
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
new file mode 100644
index 0000000..9f109a1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.res.Configuration;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link RestartDialogWindowManager}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:RestartDialogWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class RestartDialogWindowManagerTest extends ShellTestCase {
+
+    @Mock
+    private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private CompatUIConfiguration mCompatUIConfiguration;
+    @Mock private Transitions mTransitions;
+    @Mock private  Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback;
+    @Mock private  Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
+    private RestartDialogWindowManager mWindowManager;
+    private TaskInfo mTaskInfo;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTaskInfo = new ActivityManager.RunningTaskInfo();
+        mTaskInfo.configuration.uiMode =
+                (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK)
+                        | Configuration.UI_MODE_NIGHT_NO;
+        mTaskInfo.configuration.uiMode =
+                (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK)
+                        | Configuration.UI_MODE_TYPE_NORMAL;
+        mWindowManager = new RestartDialogWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+                mTaskListener, new DisplayLayout(), mTransitions, mOnRestartCallback,
+                mOnDismissCallback, mCompatUIConfiguration);
+    }
+
+    @Test
+    public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+        ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+        newTaskInfo.configuration.uiMode =
+                (newTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_TYPE_MASK)
+                        | Configuration.UI_MODE_TYPE_DESK;
+
+        Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+    }
+
+    @Test
+    public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
+        ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+        mTaskInfo.configuration.uiMode =
+                (mTaskInfo.configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK)
+                        | Configuration.UI_MODE_NIGHT_YES;
+
+        Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+    }
+}
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 8c9f65f..597cbf7 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -20,11 +20,14 @@
 #include <private/performance_hint_private.h>
 #include <utils/Log.h>
 
+#include <chrono>
 #include <vector>
 
 #include "../Properties.h"
 #include "thread/CommonPool.h"
 
+using namespace std::chrono_literals;
+
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -96,10 +99,25 @@
 }
 
 bool HintSessionWrapper::init() {
-    // If it already exists, broke last time we tried this, shouldn't be running, or
+    if (mHintSession != nullptr) return true;
+
+    // If we're waiting for the session
+    if (mHintSessionFuture.valid()) {
+        // If the session is here
+        if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
+            mHintSession = mHintSessionFuture.get();
+            if (mHintSession != nullptr) {
+                mSessionValid = true;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // If it broke last time we tried this, shouldn't be running, or
     // has bad argument values, don't even bother
-    if (mHintSession != nullptr || !mSessionValid || !Properties::useHintManager ||
-        !Properties::isDrawingEnabled() || mUiThreadId < 0 || mRenderThreadId < 0) {
+    if (!mSessionValid || !Properties::useHintManager || !Properties::isDrawingEnabled() ||
+        mUiThreadId < 0 || mRenderThreadId < 0) {
         return false;
     }
 
@@ -118,15 +136,14 @@
     // Use a placeholder target value to initialize,
     // this will always be replaced elsewhere before it gets used
     int64_t defaultTargetDurationNanos = 16666667;
-    mHintSession =
-            gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
-
-    mSessionValid = !!mHintSession;
-    return mSessionValid;
+    mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] {
+        return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+    });
+    return false;
 }
 
 void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {
-    if (mHintSession == nullptr) return;
+    if (!init()) return;
     targetWorkDurationNanos = targetWorkDurationNanos * Properties::targetCpuTimePercentage / 100;
     if (targetWorkDurationNanos != mLastTargetWorkDuration &&
         targetWorkDurationNanos > kSanityCheckLowerBound &&
@@ -138,7 +155,7 @@
 }
 
 void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
-    if (mHintSession == nullptr) return;
+    if (!init()) return;
     if (actualDurationNanos > kSanityCheckLowerBound &&
         actualDurationNanos < kSanityCheckUpperBound) {
         gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
@@ -146,7 +163,7 @@
 }
 
 void HintSessionWrapper::sendLoadResetHint() {
-    if (mHintSession == nullptr) return;
+    if (!init()) return;
     nsecs_t now = systemTime();
     if (now - mLastFrameNotification > kResetHintTimeout) {
         gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
@@ -155,7 +172,7 @@
 }
 
 void HintSessionWrapper::sendLoadIncreaseHint() {
-    if (mHintSession == nullptr) return;
+    if (!init()) return;
     gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP));
 }
 
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index f2f1298..b7a433f 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -18,6 +18,8 @@
 
 #include <android/performance_hint.h>
 
+#include <future>
+
 #include "utils/TimeUtils.h"
 
 namespace android {
@@ -38,6 +40,7 @@
 
 private:
     APerformanceHintSession* mHintSession = nullptr;
+    std::future<APerformanceHintSession*> mHintSessionFuture;
 
     nsecs_t mLastFrameNotification = 0;
     nsecs_t mLastTargetWorkDuration = 0;
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 10f4567..3168cb0 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -375,6 +375,14 @@
     }
 }
 
+void VulkanSurface::invalidateBuffers() {
+    for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) {
+        VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i];
+        bufferInfo.hasValidContents = false;
+        bufferInfo.lastPresentedCount = 0;
+    }
+}
+
 VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
     // Set the mCurrentBufferInfo to invalid in case of error and only reset it to the correct
     // value at the end of the function if everything dequeued correctly.
@@ -407,6 +415,10 @@
             // new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer.
             mWindowInfo.actualSize = actualSize;
             releaseBuffers();
+        } else {
+            // A change in transform means we need to repaint the entire buffer area as the damage
+            // rects have just moved about.
+            invalidateBuffers();
         }
 
         if (transformHint != mWindowInfo.transform) {
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index 6f528010..116075c 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -116,6 +116,7 @@
                                            WindowInfo* outWindowInfo);
     static bool UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo);
     void releaseBuffers();
+    void invalidateBuffers();
 
     // TODO: This number comes from ui/BufferQueueDefs. We're not pulling the
     // header in so that we don't need to depend on libui, but we should share
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 88e3519..e21d6fb 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -45,14 +45,13 @@
 // --- PointerController::DisplayInfoListener ---
 
 void PointerController::DisplayInfoListener::onWindowInfosChanged(
-        const std::vector<android::gui::WindowInfo>&,
-        const std::vector<android::gui::DisplayInfo>& displayInfos) {
+        const gui::WindowInfosUpdate& update) {
     std::scoped_lock lock(mLock);
     if (mPointerController == nullptr) return;
 
     // PointerController uses DisplayInfoListener's lock.
     base::ScopedLockAssertion assumeLocked(mPointerController->getLock());
-    mPointerController->onDisplayInfosChangedLocked(displayInfos);
+    mPointerController->onDisplayInfosChangedLocked(update.displayInfos);
 }
 
 void PointerController::DisplayInfoListener::onPointerControllerDestroyed() {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index ca14b6e..62ee743 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -19,6 +19,7 @@
 
 #include <PointerControllerInterface.h>
 #include <gui/DisplayEventReceiver.h>
+#include <gui/WindowInfosUpdate.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <utils/BitSet.h>
@@ -114,8 +115,7 @@
     class DisplayInfoListener : public gui::WindowInfosListener {
     public:
         explicit DisplayInfoListener(PointerController* pc) : mPointerController(pc){};
-        void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&,
-                                  const std::vector<android::gui::DisplayInfo>&) override;
+        void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
         void onPointerControllerDestroyed();
 
         // This lock is also used by PointerController. See PointerController::getLock().
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 2378d42..8574751 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -343,7 +343,7 @@
         localListenerCopy = registeredListener;
     }
     EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered";
-    localListenerCopy->onWindowInfosChanged({}, {});
+    localListenerCopy->onWindowInfosChanged({{}, {}, 0, 0});
 }
 
 }  // namespace android
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
index 531b3ae..bc6a259 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -80,14 +80,16 @@
      *   This implies that the caller must clear its caller identity to protect from the case where
      *   it resides in the same process as the callee.
      * - The identity of the entity on behalf of which module operations are to be performed.
-     *
+     * @param isTrusted - {@code true} if the middleware should not audit data delivery, since the
+     * callback is being delivered to another trusted component which will audit access.
      * listModules() must be called prior to calling this method and the provided handle must be
      * one of the handles from the returned list.
      */
     ISoundTriggerModule attachAsMiddleman(int handle,
                                           in Identity middlemanIdentity,
                                           in Identity originatorIdentity,
-                                          ISoundTriggerCallback callback);
+                                          ISoundTriggerCallback callback,
+                                          boolean isTrusted);
 
     /**
      * Attach an injection interface interface to the ST mock HAL.
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
index 18688ce..4bdefd0 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -79,8 +79,9 @@
      *
      * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
      * resources required for starting the model are currently consumed by other clients.
+     * @return - A token delivered along with future recognition events.
      */
-    void startRecognition(int modelHandle, in RecognitionConfig config);
+    IBinder startRecognition(int modelHandle, in RecognitionConfig config);
 
     /**
      * Stop a recognition of a previously active recognition. Will NOT generate a recognition event.
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
index 6c912ed..d9d16ec 100644
--- a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
@@ -33,4 +33,9 @@
      */
     // @ElapsedRealtimeLong
     long halEventReceivedMillis = -1;
+    /**
+     * Token relating this event to a particular recognition session, returned by
+     * {@link ISoundTriggerModule.startRecognition(int, RecognitionConfig}
+     */
+    IBinder token;
 }
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
index 84e327d..20ec8c2 100644
--- a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
@@ -33,4 +33,9 @@
      */
     // @ElapsedRealtimeLong
     long halEventReceivedMillis = -1;
+    /**
+     * Token relating this event to a particular recognition session, returned by
+     * {@link ISoundTriggerModule.startRecognition(int, RecognitionConfig}
+     */
+    IBinder token;
 }
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 0289aa3..3394dd0 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -203,10 +203,16 @@
 
         AudioProductStrategy thatStrategy = (AudioProductStrategy) o;
 
-        return mName == thatStrategy.mName && mId == thatStrategy.mId
+        return mId == thatStrategy.mId
+                && Objects.equals(mName, thatStrategy.mName)
                 && Arrays.equals(mAudioAttributesGroups, thatStrategy.mAudioAttributesGroups);
     }
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mName, Arrays.hashCode(mAudioAttributesGroups));
+    }
+
     /**
      * @param name of the product strategy
      * @param id of the product strategy
@@ -460,6 +466,12 @@
                     && Arrays.equals(mAudioAttributes, thatAag.mAudioAttributes);
         }
 
+        @Override
+        public int hashCode() {
+            return Objects.hash(mVolumeGroupId, mLegacyStreamType,
+                    Arrays.hashCode(mAudioAttributes));
+        }
+
         public int getStreamType() {
             return mLegacyStreamType;
         }
diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java
index 230d763..f2b772f 100644
--- a/media/java/android/media/tv/AdBuffer.java
+++ b/media/java/android/media/tv/AdBuffer.java
@@ -22,6 +22,8 @@
 import android.os.Parcelable;
 import android.os.SharedMemory;
 
+import java.io.IOException;
+
 /**
  * Buffer for advertisement data.
  */
@@ -57,6 +59,16 @@
         this.mFlags = flags;
     }
 
+    /** @hide **/
+    public static AdBuffer dupAdBuffer(AdBuffer buffer) throws IOException {
+        if (buffer == null) {
+            return null;
+        }
+        return new AdBuffer(buffer.mId, buffer.mMimeType,
+                SharedMemory.fromFileDescriptor(buffer.mBuffer.getFdDup()), buffer.mOffset,
+                buffer.mLength, buffer.mPresentationTimeUs, buffer.mFlags);
+    }
+
     /**
      * Gets corresponding AD request ID.
      *
diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java
index d8cddfc..5f6b2b5 100644
--- a/media/java/android/media/tv/AdRequest.java
+++ b/media/java/android/media/tv/AdRequest.java
@@ -72,6 +72,22 @@
     private final Bundle mMetadata;
     private final Uri mUri;
 
+    /**
+     * The key for video metadata.
+     *
+     * @see #getMetadata()
+     * @hide
+     */
+    public static final String KEY_VIDEO_METADATA = "key_video_metadata";
+
+    /**
+     * The key for audio metadata.
+     *
+     * @see #getMetadata()
+     * @hide
+     */
+    public static final String KEY_AUDIO_METADATA = "key_audio_metadata";
+
     public AdRequest(int id, @RequestType int requestType,
             @Nullable ParcelFileDescriptor fileDescriptor, long startTime, long stopTime,
             long echoInterval, @Nullable String mediaFileType, @NonNull Bundle metadata) {
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index f344fd3..631ab9a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -3737,6 +3737,10 @@
                 mService.notifyAdBufferReady(mToken, buffer, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
+            } finally {
+                if (buffer != null) {
+                    buffer.getSharedMemory().close();
+                }
             }
         }
 
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 06d1acd..7cce84a 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1608,6 +1608,10 @@
                 mService.notifyAdBufferConsumed(mToken, buffer, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
+            } finally {
+                if (buffer != null) {
+                    buffer.getSharedMemory().close();
+                }
             }
         }
 
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 06dfe4f..ec85cc7 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -69,6 +69,7 @@
 
 import com.android.internal.os.SomeArgs;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -1973,9 +1974,9 @@
                                     "notifyAdBufferReady(buffer=" + buffer + ")");
                         }
                         if (mSessionCallback != null) {
-                            mSessionCallback.onAdBufferReady(buffer);
+                            mSessionCallback.onAdBufferReady(AdBuffer.dupAdBuffer(buffer));
                         }
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | IOException e) {
                         Log.w(TAG, "error in notifyAdBuffer", e);
                     }
                 }
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
index b66545a..266faae 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
@@ -29,11 +29,14 @@
 import android.media.AudioSystem;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
+import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.google.common.testing.EqualsTester;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -231,4 +234,26 @@
             }
         }
     }
+
+    @Test
+    public void testEquals() {
+        final EqualsTester equalsTester = new EqualsTester();
+
+        AudioProductStrategy.getAudioProductStrategies().forEach(
+                strategy -> equalsTester.addEqualityGroup(strategy,
+                        writeToAndFromParcel(strategy)));
+
+        equalsTester.testEquals();
+    }
+
+    private static AudioProductStrategy writeToAndFromParcel(
+            AudioProductStrategy audioProductStrategy) {
+        Parcel parcel = Parcel.obtain();
+        audioProductStrategy.writeToParcel(parcel, /*flags=*/0);
+        parcel.setDataPosition(0);
+        AudioProductStrategy unmarshalledAudioProductStrategy =
+                AudioProductStrategy.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        return unmarshalledAudioProductStrategy;
+    }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0f5280e..78ee819 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -49,6 +49,7 @@
     private val context: Context,
     intent: Intent,
     userConfigRepo: UserConfigRepo,
+    isNewActivity: Boolean,
 ) {
     val requestInfo: RequestInfo?
     private val providerEnabledList: List<ProviderData>
@@ -125,7 +126,8 @@
                         isPasskeyFirstUse = isPasskeyFirstUse,
                     )!!,
                     getCredentialUiState = null,
-                    cancelRequestState = cancelUiRequestState
+                    cancelRequestState = cancelUiRequestState,
+                    isInitialRender = isNewActivity,
                 )
             }
             RequestInfo.TYPE_GET -> {
@@ -140,7 +142,8 @@
                     if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
                     else ProviderActivityState.READY_TO_LAUNCH,
                     isAutoSelectFlow = autoSelectEntry != null,
-                    cancelRequestState = cancelUiRequestState
+                    cancelRequestState = cancelUiRequestState,
+                    isInitialRender = isNewActivity,
                 )
             }
             else -> {
@@ -149,6 +152,7 @@
                         createCredentialUiState = null,
                         getCredentialUiState = null,
                         cancelRequestState = cancelUiRequestState,
+                        isInitialRender = isNewActivity,
                     )
                 } else {
                     throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 54a8678d..c409ba6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -62,7 +62,8 @@
                 return
             }
             val userConfigRepo = UserConfigRepo(this)
-            val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+            val credManRepo = CredentialManagerRepo(
+                this, intent, userConfigRepo, isNewActivity = true)
 
             val backPressedCallback = object : OnBackPressedCallback(
                 true // default to enabled
@@ -103,7 +104,8 @@
                 }
             } else {
                 val userConfigRepo = UserConfigRepo(this)
-                val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+                val credManRepo = CredentialManagerRepo(
+                    this, intent, userConfigRepo, isNewActivity = false)
                 viewModel.onNewCredentialManagerRepo(credManRepo)
             }
         } catch (e: Exception) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 081490e..e96e536 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -53,6 +53,7 @@
     // launched immediately, and canceling it will cancel the whole UI flow.
     val isAutoSelectFlow: Boolean = false,
     val cancelRequestState: CancelUiRequestState?,
+    val isInitialRender: Boolean,
 )
 
 data class CancelUiRequestState(
@@ -82,6 +83,10 @@
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
 
+    fun onInitialRenderComplete() {
+        uiState = uiState.copy(isInitialRender = false)
+    }
+
     fun onCancellationUiRequested(appDisplayName: String?) {
         uiState = uiState.copy(cancelRequestState = CancelUiRequestState(appDisplayName))
     }
@@ -96,7 +101,7 @@
 
     fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) {
         this.credManRepo = credManRepo
-        uiState = credManRepo.initState()
+        uiState = credManRepo.initState().copy(isInitialRender = false)
 
         if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) {
             this.uiMetrics.resetInstanceId()
@@ -114,7 +119,12 @@
             uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
             val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
                 .setFillInIntent(entry.fillInIntent).build()
-            launcher.launch(intentSenderRequest)
+            try {
+                launcher.launch(intentSenderRequest)
+            } catch (e: Exception) {
+                Log.w(Constants.LOG_TAG, "Failed to launch provider UI: $e")
+                onInternalError()
+            }
         } else {
             Log.d(Constants.LOG_TAG, "No provider UI to launch")
             onInternalError()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index a35310c..ba20a5e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -213,7 +213,7 @@
                     }
                 }
             return com.android.credentialmanager.getflow.RequestDisplayInfo(
-                appName = originName
+                appName = originName?.ifEmpty { null }
                     ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
                     ?: return null,
                 preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
@@ -286,7 +286,8 @@
                             pendingIntent = credentialEntry.pendingIntent,
                             fillInIntent = it.frameworkExtrasIntent,
                             credentialType = CredentialType.UNKNOWN,
-                            credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+                            credentialTypeDisplayName =
+                            credentialEntry.typeDisplayName?.toString().orEmpty(),
                             userName = credentialEntry.title.toString(),
                             displayName = credentialEntry.subtitle?.toString(),
                             icon = credentialEntry.icon.loadDrawable(context),
@@ -461,7 +462,7 @@
             if (requestInfo == null) {
                 return null
             }
-            val appLabel = originName
+            val appLabel = originName?.ifEmpty { null }
                 ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
                 ?: return null
             val createCredentialRequest = requestInfo.createCredentialRequest ?: return null
@@ -660,7 +661,7 @@
                     passwordCount = createEntry.getPasswordCredentialCount(),
                     passkeyCount = createEntry.getPublicKeyCredentialCount(),
                     totalCredentialCount = createEntry.getTotalCredentialCount(),
-                    lastUsedTime = createEntry.lastUsedTime,
+                    lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN,
                     footerDescription = createEntry.description?.toString()
                 ))
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index 3399681..bd4375f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -20,10 +20,6 @@
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -40,9 +36,10 @@
 @Composable
 fun ModalBottomSheet(
     sheetContent: @Composable ColumnScope.() -> Unit,
-    onDismiss: () -> Unit
+    onDismiss: () -> Unit,
+    isInitialRender: Boolean,
+    onInitialRenderComplete: () -> Unit,
 ) {
-    var isInitialRender by remember { mutableStateOf(true) }
     val scope = rememberCoroutineScope()
     val state = rememberModalBottomSheetState(
         initialValue = ModalBottomSheetValue.Hidden,
@@ -64,7 +61,7 @@
     LaunchedEffect(state.currentValue) {
         if (state.currentValue == ModalBottomSheetValue.Hidden) {
             if (isInitialRender) {
-                isInitialRender = false
+                onInitialRenderComplete()
                 scope.launch { state.show() }
             } else {
                 onDismiss()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index d16120f..a378f1c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -164,7 +164,9 @@
                 }
             }
         },
-        onDismiss = viewModel::onUserCancel
+        onDismiss = viewModel::onUserCancel,
+        isInitialRender = viewModel.uiState.isInitialRender,
+        onInitialRenderComplete = viewModel::onInitialRenderComplete,
     )
 }
 
@@ -436,7 +438,8 @@
                 },
             )
         }
-        if (createOptionInfo.footerDescription != null) {
+        val footerDescription = createOptionInfo.footerDescription
+        if (footerDescription != null && footerDescription.length > 0) {
             item {
                 Divider(
                     thickness = 1.dp,
@@ -446,7 +449,7 @@
             }
             item {
                 Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
-                    BodySmallText(text = createOptionInfo.footerDescription)
+                    BodySmallText(text = footerDescription)
                 }
             }
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index fe1ce1b..8f3f3c8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -72,7 +72,7 @@
     val passwordCount: Int?,
     val passkeyCount: Int?,
     val totalCredentialCount: Int?,
-    val lastUsedTime: Instant?,
+    val lastUsedTime: Instant,
     val footerDescription: String?,
 ) : BaseEntry(
     providerId,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ccbd46d..40c99ee 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -148,6 +148,8 @@
                 }
             },
             onDismiss = viewModel::onUserCancel,
+            isInitialRender = viewModel.uiState.isInitialRender,
+            onInitialRenderComplete = viewModel::onInitialRenderComplete,
         )
     }
 }
@@ -217,13 +219,27 @@
                     text = stringResource(
                         if (hasSingleEntry) {
                             val singleEntryType = sortedUserNameToCredentialEntryList.firstOrNull()
-                                ?.sortedCredentialEntryList?.first()?.credentialType
+                                ?.sortedCredentialEntryList?.firstOrNull()?.credentialType
                             if (singleEntryType == CredentialType.PASSKEY)
                                 R.string.get_dialog_title_use_passkey_for
                             else if (singleEntryType == CredentialType.PASSWORD)
                                 R.string.get_dialog_title_use_password_for
+                            else if (authenticationEntryList.isNotEmpty())
+                                R.string.get_dialog_title_unlock_options_for
                             else R.string.get_dialog_title_use_sign_in_for
-                        } else R.string.get_dialog_title_choose_sign_in_for,
+                        } else {
+                            if (authenticationEntryList.isNotEmpty() ||
+                                sortedUserNameToCredentialEntryList.any { perNameEntryList ->
+                                    perNameEntryList.sortedCredentialEntryList.any { entry ->
+                                        entry.credentialType != CredentialType.PASSWORD &&
+                                            entry.credentialType != CredentialType.PASSKEY
+                                    }
+                                }
+                            )
+                                R.string.get_dialog_title_choose_sign_in_for
+                            else
+                                R.string.get_dialog_title_choose_saved_sign_in_for
+                        },
                         requestDisplayInfo.appName
                     ),
                 )
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index e9000a8..df43863 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -27,6 +27,7 @@
         "//apex_available:platform",
         "com.android.adservices",
         "com.android.cellbroadcast",
+        "com.android.devicelock",
         "com.android.extservices",
         "com.android.permission",
         "com.android.healthfitness",
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index d26ad1c..e6fb720 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -16,6 +16,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.cellbroadcast",
+        "com.android.devicelock",
         "com.android.extservices",
         "com.android.permission",
         "com.android.adservices",
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index 0f9f781..48cc75d 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -22,6 +22,7 @@
         "//apex_available:platform",
         "com.android.adservices",
         "com.android.cellbroadcast",
+        "com.android.devicelock",
         "com.android.extservices",
         "com.android.permission",
         "com.android.healthfitness",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 621e6ea..0f5862a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -20,6 +20,7 @@
 
 import android.content.Intent
 import android.os.Bundle
+import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
@@ -40,6 +41,7 @@
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.NullPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@@ -51,7 +53,7 @@
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.PageWithEvent
+import com.android.settingslib.spa.framework.util.PageLogger
 import com.android.settingslib.spa.framework.util.getDestination
 import com.android.settingslib.spa.framework.util.getEntryId
 import com.android.settingslib.spa.framework.util.getSessionName
@@ -87,25 +89,50 @@
         setContent {
             SettingsTheme {
                 val sppRepository by spaEnvironment.pageProviderRepository
-                BrowseContent(sppRepository, intent)
+                BrowseContent(
+                    sppRepository = sppRepository,
+                    isPageEnabled = ::isPageEnabled,
+                    initialIntent = intent,
+                )
             }
         }
     }
+
+    open fun isPageEnabled(page: SettingsPage) = page.isEnabled()
 }
 
 @VisibleForTesting
 @Composable
-fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
+internal fun BrowseContent(
+    sppRepository: SettingsPageProviderRepository,
+    isPageEnabled: (SettingsPage) -> Boolean,
+    initialIntent: Intent?,
+) {
     val navController = rememberAnimatedNavController()
     CompositionLocalProvider(navController.localNavController()) {
         val controller = LocalNavController.current as NavControllerWrapperImpl
-        controller.NavContent(sppRepository.getAllProviders())
+        controller.NavContent(sppRepository.getAllProviders()) { page ->
+            if (remember { isPageEnabled(page) }) {
+                LaunchedEffect(Unit) {
+                    Log.d(TAG, "Launching page ${page.sppName}")
+                }
+                page.PageLogger()
+                page.UiLayout()
+            } else {
+                LaunchedEffect(Unit) {
+                    controller.navigateBack()
+                }
+            }
+        }
         controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage())
     }
 }
 
 @Composable
-private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
+private fun NavControllerWrapperImpl.NavContent(
+    allProvider: Collection<SettingsPageProvider>,
+    content: @Composable (SettingsPage) -> Unit,
+) {
     AnimatedNavHost(
         navController = navController,
         startDestination = NullPageProvider.name,
@@ -139,7 +166,7 @@
                 },
             ) { navBackStackEntry ->
                 val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
-                page.PageWithEvent()
+                content(page)
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index dde4e04..a9e5e39 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -29,14 +29,12 @@
 import com.android.settingslib.spa.framework.compose.NavControllerWrapper
 
 @Composable
-internal fun SettingsPage.PageWithEvent() {
-    if (!isEnabled()) return
+internal fun SettingsPage.PageLogger() {
     val navController = LocalNavController.current
     LifecycleEffect(
         onStart = { logPageEvent(LogEvent.PAGE_ENTER, navController) },
         onStop = { logPageEvent(LogEvent.PAGE_LEAVE, navController) },
     )
-    UiLayout()
 }
 
 private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index 218f569..92d3411 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
@@ -38,8 +39,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-const val WAIT_UNTIL_TIMEOUT = 1000L
-
 @RunWith(AndroidJUnit4::class)
 class BrowseActivityTest {
     @get:Rule
@@ -49,19 +48,26 @@
     private val spaLogger = SpaLoggerForTest()
 
     @Test
-    fun testBrowsePage() {
-        spaLogger.reset()
-        val spaEnvironment =
-            SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
+    fun browseContent_onNavigate_logPageEvent() {
+        val spaEnvironment = SpaEnvironmentForTest(
+            context = context,
+            rootPages = listOf(SppHome.createSettingsPage()),
+            logger = spaLogger,
+        )
         SpaEnvironmentFactory.reset(spaEnvironment)
-
         val sppRepository by spaEnvironment.pageProviderRepository
         val sppHome = sppRepository.getProviderOrNull("SppHome")!!
         val pageHome = sppHome.createSettingsPage()
         val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
         val pageLayer1 = sppLayer1.createSettingsPage()
 
-        composeTestRule.setContent { BrowseContent(sppRepository) }
+        composeTestRule.setContent {
+            BrowseContent(
+                sppRepository = sppRepository,
+                isPageEnabled = SettingsPage::isEnabled,
+                initialIntent = null,
+            )
+        }
 
         composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
         spaLogger.verifyPageEvent(pageHome.id, 1, 0)
@@ -69,7 +75,7 @@
 
         // click to layer1 page
         composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick()
-        waitUntil(WAIT_UNTIL_TIMEOUT) {
+        waitUntil {
             composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null))
                 .fetchSemanticsNodes().size == 1
         }
@@ -78,18 +84,24 @@
     }
 
     @Test
-    fun testBrowseDisabledPage() {
-        spaLogger.reset()
+    fun browseContent_whenDisabled_noLogPageEvent() {
         val spaEnvironment = SpaEnvironmentForTest(
-            context, listOf(SppDisabled.createSettingsPage()), logger = spaLogger
+            context = context,
+            rootPages = listOf(SppDisabled.createSettingsPage()),
+            logger = spaLogger,
         )
         SpaEnvironmentFactory.reset(spaEnvironment)
-
         val sppRepository by spaEnvironment.pageProviderRepository
         val sppDisabled = sppRepository.getProviderOrNull("SppDisabled")!!
         val pageDisabled = sppDisabled.createSettingsPage()
 
-        composeTestRule.setContent { BrowseContent(sppRepository) }
+        composeTestRule.setContent {
+            BrowseContent(
+                sppRepository = sppRepository,
+                isPageEnabled = SettingsPage::isEnabled,
+                initialIntent = null,
+            )
+        }
 
         composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
         spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
index 5396de0..17c139b 100644
--- a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -18,6 +18,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.spaprivileged">
     <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
 </manifest>
 
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 338b16d..e9da88e 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
@@ -46,7 +46,7 @@
 ) : AppRecord
 
 abstract class AppOpPermissionListModel(
-    private val context: Context,
+    protected val context: Context,
     private val packageManagers: IPackageManagers = PackageManagers,
 ) : TogglePermissionAppListModel<AppOpPermissionRecord> {
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 23270c1..eb3d7dc 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -55,6 +55,7 @@
     @Before
     fun setUp() {
         whenever(context.appOpsManager).thenReturn(appOpsManager)
+        whenever(context.packageManager).thenReturn(packageManager)
         doNothing().`when`(packageManager)
                 .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
     }
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 af59a55..c54f4f8 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
@@ -19,6 +19,7 @@
 import android.app.AppOpsManager
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
 import androidx.compose.runtime.State
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.lifecycle.MutableLiveData
@@ -38,6 +39,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.Spy
@@ -57,11 +62,16 @@
 
     @Mock private lateinit var appOpsManager: AppOpsManager
 
+    @Mock private lateinit var packageManager: PackageManager
+
     private lateinit var listModel: TestAppOpPermissionAppListModel
 
     @Before
     fun setUp() {
         whenever(context.appOpsManager).thenReturn(appOpsManager)
+        whenever(context.packageManager).thenReturn(packageManager)
+        doNothing().`when`(packageManager)
+                .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
         listModel = TestAppOpPermissionAppListModel()
     }
 
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index eca1165..5d09a1a 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -23,6 +23,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.cellbroadcast",
+        "com.android.devicelock",
         "com.android.healthfitness",
     ],
 }
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index 33ba64a..dc2b52d 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -25,6 +25,7 @@
         "//apex_available:platform",
         "com.android.adservices",
         "com.android.cellbroadcast",
+        "com.android.devicelock",
         "com.android.extservices",
         "com.android.healthfitness",
         "com.android.permission",
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index e9aded0..2372c80 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -67,6 +67,8 @@
 
     <!-- SignalDrawable -->
     <dimen name="signal_icon_size">15dp</dimen>
+    <!-- The size of the roaming icon in the top-left corner of the signal icon. -->
+    <dimen name="signal_icon_size_roaming">8dp</dimen>
 
     <!-- Size of nearby icon -->
     <dimen name="bt_nearby_icon_size">24dp</dimen>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 214c903..4e75792 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -244,7 +244,7 @@
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing Aid profile. -->
     <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the LE audio profile. -->
-    <string name="bluetooth_profile_le_audio">LE audio</string>
+    <string name="bluetooth_profile_le_audio">LE audio (experimental)</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
     <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE audio checkbox preference when LE audio is connected. -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index ac9cdac..e034254 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -95,9 +95,6 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if (!isLaidOut()) {
-            return;
-        }
         if (mMaskBitmap == null) {
             mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
             mMaskCanvas = new Canvas(mMaskBitmap);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index adaf4a1..c45d774 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -343,7 +343,12 @@
     }
 
     @Nullable
-    private WifiInfo getMainOrUnderlyingWifiInfo(NetworkCapabilities networkCapabilities) {
+    private WifiInfo getMainOrUnderlyingWifiInfo(
+            @Nullable NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities == null) {
+            return null;
+        }
+
         WifiInfo mainWifiInfo = getMainWifiInfo(networkCapabilities);
         if (mainWifiInfo != null) {
             return mainWifiInfo;
@@ -376,7 +381,10 @@
     }
 
     @Nullable
-    private WifiInfo getMainWifiInfo(NetworkCapabilities networkCapabilities) {
+    private WifiInfo getMainWifiInfo(@Nullable NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities == null) {
+            return null;
+        }
         boolean canHaveWifiInfo = networkCapabilities.hasTransport(TRANSPORT_WIFI)
                 || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
         if (!canHaveWifiInfo) {
@@ -402,7 +410,11 @@
                 getMainOrUnderlyingWifiInfo(networkCapabilities));
     }
 
-    private boolean connectionIsWifi(NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) {
+    private boolean connectionIsWifi(
+            @Nullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) {
+        if (networkCapabilities == null) {
+            return false;
+        }
         return wifiInfo != null || networkCapabilities.hasTransport(TRANSPORT_WIFI);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
index 6e975cf..5a9a9d1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
@@ -305,4 +305,16 @@
 
         assertThat(mWifiStatusTracker.isDefaultNetwork).isTrue();
     }
+
+    /** Regression test for b/280169520. */
+    @Test
+    public void networkCallbackNullCapabilities_noCrash() {
+        Network primaryNetwork = Mockito.mock(Network.class);
+
+        // WHEN the network capabilities are null
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(
+                primaryNetwork, /* networkCapabilities= */ null);
+
+        // THEN there's no crash (no assert needed)
+    }
 }
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index 29832a0..c85449d0 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -78,16 +78,10 @@
                 android:id="@+id/mobile_roaming"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_gravity="top|start"
                 android:src="@drawable/stat_sys_roaming"
                 android:contentDescription="@string/data_connection_roaming"
                 android:visibility="gone" />
         </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_roaming_large"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/stat_sys_roaming_large"
-            android:contentDescription="@string/data_connection_roaming"
-            android:visibility="gone" />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
 </merge>
diff --git a/packages/SystemUI/res/drawable/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml
index 745470f..9492472 100644
--- a/packages/SystemUI/res/drawable/action_chip_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_background.xml
@@ -20,7 +20,7 @@
     android:color="@color/overlay_button_ripple">
     <item android:id="@android:id/background">
         <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/colorAccentSecondary"/>
+            <solid android:color="?androidprv:attr/materialColorSecondary"/>
             <corners android:radius="@dimen/overlay_button_corner_radius"/>
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/action_chip_container_background.xml b/packages/SystemUI/res/drawable/action_chip_container_background.xml
index 36083f1..2ee2710 100644
--- a/packages/SystemUI/res/drawable/action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_container_background.xml
@@ -18,6 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorSurface"/>
+    <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
     <corners android:radius="@dimen/overlay_action_container_corner_radius"/>
 </shape>
diff --git a/packages/SystemUI/res/drawable/overlay_cancel.xml b/packages/SystemUI/res/drawable/circular_background.xml
similarity index 62%
rename from packages/SystemUI/res/drawable/overlay_cancel.xml
rename to packages/SystemUI/res/drawable/circular_background.xml
index 3fa12dd..4fef0d6 100644
--- a/packages/SystemUI/res/drawable/overlay_cancel.xml
+++ b/packages/SystemUI/res/drawable/circular_background.xml
@@ -16,14 +16,11 @@
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        android:width="48dp"
-        android:height="48dp"
-        android:viewportWidth="32.0"
-        android:viewportHeight="32.0">
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <path
-        android:fillColor="?androidprv:attr/colorAccentTertiary"
-        android:pathData="M16,16m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"/>
-    <path
-        android:fillColor="?attr/overlayButtonTextColor"
-        android:pathData="M23,10.41L21.59,9 16,14.59 10.41,9 9,10.41 14.59,16 9,21.59 10.41,23 16,17.41 21.59,23 23,21.59 17.41,16z"/>
-</vector>
+        android:fillColor="#ff000000"
+        android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"/>
+   </vector>
diff --git a/packages/SystemUI/res/drawable/overlay_border.xml b/packages/SystemUI/res/drawable/overlay_border.xml
index c1accdc..a59f923 100644
--- a/packages/SystemUI/res/drawable/overlay_border.xml
+++ b/packages/SystemUI/res/drawable/overlay_border.xml
@@ -18,6 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorSurface"/>
-    <corners android:radius="24dp"/>
+    <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+    <corners android:radius="16dp"/>
 </shape>
diff --git a/packages/SystemUI/res/drawable/overlay_button_background.xml b/packages/SystemUI/res/drawable/overlay_button_background.xml
index c045048..4e5b8fb 100644
--- a/packages/SystemUI/res/drawable/overlay_button_background.xml
+++ b/packages/SystemUI/res/drawable/overlay_button_background.xml
@@ -17,12 +17,11 @@
 <!-- Button background for activities downstream of overlays
      (clipboard text editor, long screenshots) -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         android:color="@color/overlay_button_ripple">
     <item android:id="@android:id/background">
         <inset android:insetTop="4dp" android:insetBottom="4dp">
             <shape android:shape="rectangle">
-                <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+                <solid android:color="#fff"/>
                 <corners android:radius="20dp"/>
                 <size android:height="40dp"/>
             </shape>
@@ -31,7 +30,7 @@
     <item android:id="@android:id/mask">
         <inset android:insetTop="4dp" android:insetBottom="4dp">
             <shape android:shape="rectangle">
-                <solid android:color="?android:textColorPrimary"/>
+                <solid android:color="#000"/>
                 <corners android:radius="20dp"/>
                 <size android:height="40dp"/>
             </shape>
diff --git a/packages/SystemUI/res/drawable/overlay_button_outline.xml b/packages/SystemUI/res/drawable/overlay_button_outline.xml
new file mode 100644
index 0000000..4d91503
--- /dev/null
+++ b/packages/SystemUI/res/drawable/overlay_button_outline.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!-- Button background for activities downstream of overlays
+     (clipboard text editor, long screenshots) -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/overlay_button_ripple">
+    <item android:id="@android:id/background">
+        <inset android:insetTop="4dp" android:insetBottom="4dp">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/transparent" />
+                <stroke android:width="1dp" android:color="#fff"/>
+                <corners android:radius="20dp"/>
+                <size android:height="40dp"/>
+            </shape>
+        </inset>
+    </item>
+    <item android:id="@android:id/mask">
+        <inset android:insetTop="4dp" android:insetBottom="4dp">
+            <shape android:shape="rectangle">
+                <solid android:color="#000"/>
+                <corners android:radius="20dp"/>
+                <size android:height="40dp"/>
+            </shape>
+        </inset>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/overlay_preview_background.xml b/packages/SystemUI/res/drawable/overlay_preview_background.xml
index 5adfaa13..d39d71e 100644
--- a/packages/SystemUI/res/drawable/overlay_preview_background.xml
+++ b/packages/SystemUI/res/drawable/overlay_preview_background.xml
@@ -17,5 +17,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
-    <corners android:radius="20dp"/>
+    <!-- preview radius should be equal to [overlay border radius - overlay border width] -->
+    <corners android:radius="12dp"/>
 </shape>
diff --git a/packages/SystemUI/res/drawable/screenshot_edit_background.xml b/packages/SystemUI/res/drawable/screenshot_edit_background.xml
index a1185a2..07e5aff 100644
--- a/packages/SystemUI/res/drawable/screenshot_edit_background.xml
+++ b/packages/SystemUI/res/drawable/screenshot_edit_background.xml
@@ -20,13 +20,7 @@
         android:color="@color/overlay_button_ripple">
     <item android:id="@android:id/background">
         <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/colorAccentPrimary"/>
-            <corners android:radius="16dp"/>
-        </shape>
-    </item>
-    <item android:id="@android:id/mask">
-        <shape android:shape="rectangle">
-            <solid android:color="?android:textColorPrimary"/>
+            <solid android:color="?androidprv:attr/materialColorSecondaryFixedDim"/>
             <corners android:radius="16dp"/>
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/stat_sys_roaming.xml b/packages/SystemUI/res/drawable/stat_sys_roaming.xml
index 0dd9f5a..2dd12ca 100644
--- a/packages/SystemUI/res/drawable/stat_sys_roaming.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_roaming.xml
@@ -14,10 +14,10 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/signal_icon_size"
-        android:height="@dimen/signal_icon_size"
-        android:viewportWidth="17"
-        android:viewportHeight="17">
+        android:width="@dimen/signal_icon_size_roaming"
+        android:height="@dimen/signal_icon_size_roaming"
+        android:viewportWidth="8"
+        android:viewportHeight="8">
 
 <path
     android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index 5155b77..2459eea 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -87,19 +87,4 @@
         tools:background="?android:colorBackground"
         tools:minHeight="100dp"
         tools:minWidth="100dp" />
-
-    <com.android.systemui.screenshot.MagnifierView
-        android:id="@+id/magnifier"
-        android:visibility="invisible"
-        android:layout_width="200dp"
-        android:layout_height="200dp"
-        android:elevation="2dp"
-        app:layout_constraintTop_toTopOf="@id/preview"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
-        app:handleColor="?android:attr/colorAccent"
-        app:scrimColor="?android:colorBackgroundFloating"
-        app:scrimAlpha="128"
-        app:borderThickness="4dp"
-        app:borderColor="#fff" />
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index be76405..b3b40f3 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -24,6 +24,7 @@
         android:singleLine="true"
         android:marqueeRepeatLimit="1"
         android:ellipsize="marquee"
+        android:importantForAccessibility="no"
         style="@style/TextAppearance.AuthCredential.Title"/>
 
     <TextView
@@ -34,6 +35,7 @@
         android:singleLine="true"
         android:marqueeRepeatLimit="1"
         android:ellipsize="marquee"
+        android:importantForAccessibility="no"
         style="@style/TextAppearance.AuthCredential.Subtitle"/>
 
     <TextView
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
index cb7f40f..ae24313 100644
--- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -15,6 +16,8 @@
         android:paddingHorizontal="16dp"
         android:background="@drawable/overlay_button_background"
         android:text="@string/clipboard_edit_text_done"
+        android:backgroundTint="?androidprv:attr/materialColorPrimary"
+        android:textColor="?androidprv:attr/materialColorOnPrimary"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 297cf2b..2500769 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -179,6 +179,10 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_margin="@dimen/overlay_dismiss_button_margin"
-            android:src="@drawable/overlay_cancel"/>
+            android:background="@drawable/circular_background"
+            android:backgroundTint="?androidprv:attr/materialColorPrimaryFixedDim"
+            android:tint="?androidprv:attr/materialColorOnPrimaryFixed"
+            android:padding="4dp"
+            android:src="@drawable/ic_close"/>
     </FrameLayout>
 </com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 2927d6b..8a19c2e 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -16,9 +16,9 @@
   -->
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:background="?android:colorBackgroundFloating"
     android:id="@+id/root"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -32,7 +32,8 @@
         android:layout_marginStart="8dp"
         android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
         android:background="@drawable/overlay_button_background"
-        android:textColor="?android:textColorSecondary"
+        android:backgroundTint="?androidprv:attr/materialColorPrimary"
+        android:textColor="?androidprv:attr/materialColorOnPrimary"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toTopOf="@id/preview" />
@@ -45,8 +46,9 @@
         android:text="@android:string/cancel"
         android:layout_marginStart="6dp"
         android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
-        android:background="@drawable/overlay_button_background"
-        android:textColor="?android:textColorSecondary"
+        android:background="@drawable/overlay_button_outline"
+        android:backgroundTint="?androidprv:attr/materialColorPrimary"
+        android:textColor="?androidprv:attr/materialColorOnSurface"
         app:layout_constraintStart_toEndOf="@id/save"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toTopOf="@id/preview"
@@ -55,7 +57,7 @@
     <ImageButton
         android:id="@+id/share"
         style="@android:style/Widget.Material.Button.Borderless"
-        android:tint="?android:textColorPrimary"
+        android:tint="?androidprv:attr/materialColorOnSurface"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginEnd="8dp"
@@ -112,10 +114,10 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:handleThickness="@dimen/screenshot_crop_handle_thickness"
-        app:handleColor="?android:attr/colorAccent"
-        app:scrimColor="?android:colorBackgroundFloating"
+        app:handleColor="?androidprv:attr/materialColorSecondary"
+        app:scrimColor="?androidprv:attr/materialColorSurfaceContainer"
         app:scrimAlpha="128"
-        app:containerBackgroundColor="?android:colorBackgroundFloating"
+        app:containerBackgroundColor="?androidprv:attr/materialColorSurfaceContainer"
         tools:background="?android:colorBackground"
         tools:minHeight="100dp"
         tools:minWidth="100dp" />
@@ -129,12 +131,11 @@
         app:layout_constraintTop_toTopOf="@id/preview"
         app:layout_constraintLeft_toLeftOf="parent"
         app:handleThickness="@dimen/screenshot_crop_handle_thickness"
-        app:handleColor="?android:attr/colorAccent"
-        app:scrimColor="?android:colorBackgroundFloating"
+        app:handleColor="?androidprv:attr/materialColorSecondary"
+        app:scrimColor="?androidprv:attr/materialColorSurfaceContainer"
         app:scrimAlpha="128"
         app:borderThickness="4dp"
-        app:borderColor="#fff"
-        />
+        app:borderColor="?androidprv:attr/materialColorSurfaceBright" />
 
     <ImageButton
         android:id="@+id/edit"
@@ -146,12 +147,11 @@
         android:background="@drawable/screenshot_edit_background"
         android:src="@drawable/ic_screenshot_edit"
         android:contentDescription="@string/screenshot_edit_label"
-        android:tint="?android:textColorSecondary"
+        android:tint="?androidprv:attr/materialColorOnSecondaryFixed"
         android:padding="16dp"
         android:scaleType="fitCenter"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-    />
+        app:layout_constraintEnd_toEndOf="parent"/>
 
     <ImageView
         android:id="@+id/transition"
@@ -160,7 +160,6 @@
         app:layout_constraintTop_toTopOf="@id/preview"
         app:layout_constraintLeft_toLeftOf="parent"
         android:scaleType="centerCrop"
-        android:visibility="invisible"
-        />
+        android:visibility="invisible" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/media_session_end_dialog.xml b/packages/SystemUI/res/layout/media_session_end_dialog.xml
new file mode 100644
index 0000000..e1050f6
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_session_end_dialog.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/end_session_dialog"
+    android:layout_width="@dimen/large_dialog_width"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/Widget.SliceView.Panel"
+        android:gravity="center_vertical|center_horizontal"
+        android:layout_marginTop="@dimen/dialog_top_padding"
+        android:layout_marginBottom="@dimen/dialog_bottom_padding"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/end_icon"
+            android:gravity="center_vertical|center_horizontal"
+            android:layout_width="36dp"
+            android:layout_height="36dp"
+            android:importantForAccessibility="no"/>
+
+        <TextView
+            android:id="@+id/end_session_dialog_title"
+            android:text="@string/media_output_end_session_dialog_summary"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="@dimen/dialog_side_padding"
+            android:layout_marginStart="@dimen/dialog_side_padding"
+            android:layout_marginEnd="@dimen/dialog_bottom_padding"
+            android:ellipsize="end"
+            android:gravity="center_vertical|center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textSize="24sp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="end|center_vertical"
+        android:layout_marginTop="8dp"
+        android:layout_marginStart="@dimen/dialog_side_padding"
+        android:layout_marginEnd="@dimen/dialog_side_padding"
+        android:layout_marginBottom="@dimen/dialog_bottom_padding"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:text="@string/cancel"
+            android:ellipsize="end"
+            android:layout_gravity="end|center_vertical"
+            android:singleLine="true"
+            style="@style/Widget.Dialog.Button.BorderButton"
+            android:clickable="true"
+            android:focusable="true"/>
+        <Button
+            android:id="@+id/stop_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end|center_vertical"
+            android:text="@string/media_output_end_session_dialog_stop"
+            style="@style/Widget.Dialog.Button"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:clickable="true"
+            android:focusable="true"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
index 5552020..bfd079b 100644
--- a/packages/SystemUI/res/layout/mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -77,11 +77,4 @@
             android:contentDescription="@string/data_connection_roaming"
             android:visibility="gone" />
     </FrameLayout>
-    <ImageView
-        android:id="@+id/mobile_roaming_large"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/stat_sys_roaming_large"
-        android:contentDescription="@string/data_connection_roaming"
-        android:visibility="gone" />
 </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/overlay_action_chip.xml b/packages/SystemUI/res/layout/overlay_action_chip.xml
index e0c20ff..e7c382f 100644
--- a/packages/SystemUI/res/layout/overlay_action_chip.xml
+++ b/packages/SystemUI/res/layout/overlay_action_chip.xml
@@ -16,12 +16,12 @@
   -->
 <com.android.systemui.screenshot.OverlayActionChip
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/overlay_action_chip"
     android:theme="@style/FloatingOverlay"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_marginStart="@dimen/overlay_action_chip_margin_start"
-    android:paddingVertical="@dimen/overlay_action_chip_margin_vertical"
     android:layout_gravity="center"
     android:gravity="center"
     android:alpha="0.0">
@@ -33,7 +33,7 @@
         android:gravity="center">
         <ImageView
             android:id="@+id/overlay_action_chip_icon"
-            android:tint="?attr/overlayButtonTextColor"
+            android:tint="?androidprv:attr/materialColorOnSecondary"
             android:layout_width="@dimen/overlay_action_chip_icon_size"
             android:layout_height="@dimen/overlay_action_chip_icon_size"/>
         <TextView
@@ -42,6 +42,6 @@
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
             android:textSize="@dimen/overlay_action_chip_text_size"
-            android:textColor="?attr/overlayButtonTextColor"/>
+            android:textColor="?androidprv:attr/materialColorOnSecondary"/>
     </LinearLayout>
 </com.android.systemui.screenshot.OverlayActionChip>
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 7e9202c..3b728a9 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -16,6 +16,7 @@
   -->
 <com.android.systemui.screenshot.DraggableConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -68,7 +69,7 @@
         android:layout_marginTop="@dimen/overlay_border_width_neg"
         android:layout_marginEnd="@dimen/overlay_border_width_neg"
         android:layout_marginBottom="@dimen/overlay_preview_container_margin"
-        android:elevation="7dp"
+        android:elevation="8dp"
         android:alpha="0"
         android:background="@drawable/overlay_border"
         app:layout_constraintStart_toStartOf="@id/actions_container_background"
@@ -83,7 +84,7 @@
         android:layout_marginStart="@dimen/overlay_border_width"
         android:layout_marginBottom="@dimen/overlay_border_width"
         android:layout_gravity="center"
-        android:elevation="7dp"
+        android:elevation="8dp"
         android:contentDescription="@string/screenshot_edit_description"
         android:scaleType="fitEnd"
         android:background="@drawable/overlay_preview_background"
@@ -93,17 +94,17 @@
         app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
     <ImageView
         android:id="@+id/screenshot_badge"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
+        android:layout_width="56dp"
+        android:layout_height="56dp"
         android:visibility="gone"
-        android:elevation="8dp"
+        android:elevation="9dp"
         app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
     <FrameLayout
         android:id="@+id/screenshot_dismiss_button"
         android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
         android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
-        android:elevation="10dp"
+        android:elevation="11dp"
         android:visibility="gone"
         app:layout_constraintStart_toEndOf="@id/screenshot_preview"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
@@ -115,7 +116,11 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_margin="@dimen/overlay_dismiss_button_margin"
-            android:src="@drawable/overlay_cancel"/>
+            android:background="@drawable/circular_background"
+            android:backgroundTint="?androidprv:attr/materialColorPrimary"
+            android:tint="?androidprv:attr/materialColorOnPrimary"
+            android:padding="4dp"
+            android:src="@drawable/ic_close"/>
     </FrameLayout>
     <ImageView
         android:id="@+id/screenshot_scrollable_preview"
@@ -150,8 +155,7 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintWidth_max="450dp"
-        app:layout_constraintHorizontal_bias="0"
-        >
+        app:layout_constraintHorizontal_bias="0">
         <include layout="@layout/screenshot_work_profile_first_run" />
         <include layout="@layout/screenshot_detection_notice" />
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index 392d845..78cd718 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/work_profile_first_run"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
@@ -33,9 +34,13 @@
         android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
         android:contentDescription="@string/screenshot_dismiss_work_profile">
         <ImageView
-            android:layout_width="24dp"
-            android:layout_height="24dp"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
             android:layout_gravity="center"
-            android:src="@drawable/overlay_cancel"/>
+            android:background="@drawable/circular_background"
+            android:backgroundTint="?androidprv:attr/materialColorSurfaceContainerHigh"
+            android:tint="?androidprv:attr/materialColorOnSurface"
+            android:padding="2dp"
+            android:src="@drawable/ic_close"/>
     </FrameLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_preview.xml b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml
new file mode 100644
index 0000000..c068b7b
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/accessibility_fingerprint_label"
+    android:background="@drawable/fingerprint_bg">
+
+    <!-- LockScreen fingerprint icon from 0 stroke width to full width -->
+    <com.airbnb.lottie.LottieAnimationView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:scaleType="centerCrop"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintHeight_percent="0.5"
+        app:layout_constraintWidth_percent="0.5"
+        app:lottie_autoPlay="false"
+        app:lottie_loop="false"
+        app:lottie_progress="1.0"
+        app:lottie_colorFilter="?android:attr/textColorPrimary"
+        app:lottie_rawRes="@raw/udfps_lockscreen_fp" />
+</androidx.constraintlayout.widget.ConstraintLayout >
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aff0e80..bf0b8a6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -342,23 +342,22 @@
 
 
     <dimen name="screenshot_crop_handle_thickness">3dp</dimen>
-    <dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
+    <dimen name="long_screenshot_action_bar_top_margin">4dp</dimen>
 
     <!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
     <!-- Constrained size of the floating overlay preview -->
     <dimen name="overlay_x_scale">80dp</dimen>
     <!-- Radius of the chip background on floating overlay actions -->
-    <dimen name="overlay_button_corner_radius">8dp</dimen>
+    <dimen name="overlay_button_corner_radius">16dp</dimen>
     <!-- Margin between successive chips -->
     <dimen name="overlay_action_chip_margin_start">8dp</dimen>
-    <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
-    <dimen name="overlay_action_chip_margin_vertical">4dp</dimen>
-    <dimen name="overlay_action_chip_padding_vertical">11dp</dimen>
-    <dimen name="overlay_action_chip_icon_size">18sp</dimen>
+    <dimen name="overlay_action_chip_padding_vertical">12dp</dimen>
+    <dimen name="overlay_action_chip_icon_size">24sp</dimen>
     <!-- Padding on each side of the icon for icon-only chips -->
-    <dimen name="overlay_action_chip_icon_only_padding_horizontal">14dp</dimen>
+    <dimen name="overlay_action_chip_icon_only_padding_horizontal">12dp</dimen>
     <!-- Padding at the edges of the chip for icon-and-text chips -->
-    <dimen name="overlay_action_chip_padding_horizontal">12dp</dimen>
+    <dimen name="overlay_action_chip_padding_start">12dp</dimen>
+    <dimen name="overlay_action_chip_padding_end">16dp</dimen>
     <!-- Spacing between chip icon and chip text -->
     <dimen name="overlay_action_chip_spacing">8dp</dimen>
     <dimen name="overlay_action_chip_text_size">14sp</dimen>
@@ -368,8 +367,8 @@
     <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
     <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
     <dimen name="overlay_bg_protection_height">242dp</dimen>
-    <dimen name="overlay_action_container_corner_radius">18dp</dimen>
-    <dimen name="overlay_action_container_padding_vertical">4dp</dimen>
+    <dimen name="overlay_action_container_corner_radius">20dp</dimen>
+    <dimen name="overlay_action_container_padding_vertical">8dp</dimen>
     <dimen name="overlay_action_container_padding_right">8dp</dimen>
     <dimen name="overlay_action_container_padding_end">8dp</dimen>
     <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c57fef1..70fdc20 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2684,6 +2684,10 @@
     <string name="media_output_group_title_speakers_and_displays">Speakers &amp; Displays</string>
     <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
     <string name="media_output_group_title_suggested_device">Suggested Devices</string>
+    <!-- Summary for end session dialog. [CHAR LIMIT=NONE] -->
+    <string name="media_output_end_session_dialog_summary">Stop your shared session to move media to another device</string>
+    <!-- Button text for stopping session [CHAR LIMIT=60] -->
+    <string name="media_output_end_session_dialog_stop">Stop</string>
 
 
     <!-- Media Output Broadcast Dialog -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9d0cc11..cee2135 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -782,10 +782,12 @@
     </style>
 
     <style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowLightStatusBar">true</item>
         <item name="android:windowLightNavigationBar">true</item>
-        <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
+        <item name="android:statusBarColor">?androidprv:attr/materialColorSurfaceContainer</item>
+        <item name="android:navigationBarColor">?androidprv:attr/materialColorSurfaceContainerHighest</item>
         <item name="android:windowActivityTransitions">true</item>
     </style>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index d8bf570..676f342 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -179,6 +179,20 @@
     }
 
     /**
+     * Set alpha directly to mView will clip clock, so we set alpha to clock face instead
+     */
+    public void setAlpha(float alpha) {
+        ClockController clock = getClock();
+        if (clock != null) {
+            clock.getLargeClock().getView().setAlpha(alpha);
+            clock.getSmallClock().getView().setAlpha(alpha);
+        }
+        if (mStatusArea != null) {
+            mStatusArea.setAlpha(alpha);
+        }
+    }
+
+    /**
      * Attach the controller to the view it relates to.
      */
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 0982030..58807e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -184,6 +185,7 @@
         }
         mAppearAnimator.setDuration(ANIMATION_DURATION);
         mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
+        mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR));
         mAppearAnimator.start();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 693268d..1cbcb9d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -264,8 +264,7 @@
          */
         @Override
         public void finish(boolean strongAuth, int targetUserId) {
-            if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)
-                    && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) {
+            if (!mKeyguardStateController.canDismissLockScreen() && !strongAuth) {
                 Log.e(TAG,
                         "Tried to dismiss keyguard when lockscreen is not dismissible and user "
                                 + "was not authenticated with a primary security method "
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d8e1eb0f..2313609 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -135,4 +135,31 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         Trace.endSection();
     }
+
+    /**
+     * Clock content will be clipped when goes beyond bounds,
+     * so we setAlpha for all views except clock
+     */
+    public void setAlpha(float alpha, boolean excludeClock) {
+        if (!excludeClock) {
+            setAlpha(alpha);
+            return;
+        }
+        if (alpha == 1 || alpha == 0) {
+            setAlpha(alpha);
+        }
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child == mStatusViewContainer) {
+                for (int j = 0; j < mStatusViewContainer.getChildCount(); j++) {
+                    View innerChild = mStatusViewContainer.getChildAt(j);
+                    if (innerChild != mClockView) {
+                        innerChild.setAlpha(alpha);
+                    }
+                }
+            } else {
+                child.setAlpha(alpha);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 794eeda..af47466 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -180,7 +180,8 @@
      */
     public void setAlpha(float alpha) {
         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
-            mView.setAlpha(alpha);
+            mView.setAlpha(alpha, true);
+            mKeyguardClockSwitchController.setAlpha(alpha);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8578845..70c39df 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -31,9 +31,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Dumpable;
-import android.util.DumpableContainer;
 import android.util.Log;
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
@@ -57,18 +54,12 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements
-        SystemUIAppComponentFactory.ContextInitializer, DumpableContainer {
+        SystemUIAppComponentFactory.ContextInitializer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
 
     private BootCompleteCacheImpl mBootCompleteCache;
-    private DumpManager mDumpManager;
-
-    /**
-     * Map of dumpables added externally.
-     */
-    private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
 
     /**
      * Hold a reference on the stuff we start.
@@ -233,7 +224,7 @@
             }
         }
 
-        mDumpManager = mSysUIComponent.createDumpManager();
+        DumpManager dumpManager = mSysUIComponent.createDumpManager();
 
         Log.v(TAG, "Starting SystemUI services for user " +
                 Process.myUserHandle().getIdentifier() + ".");
@@ -267,7 +258,7 @@
                 notifyBootCompleted(mServices[i]);
             }
 
-            mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
+            dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
         }
         mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
@@ -342,36 +333,6 @@
         return startable;
     }
 
-    // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
-    @Override
-    public boolean addDumpable(Dumpable dumpable) {
-        String name = dumpable.getDumpableName();
-        if (mDumpables.containsKey(name)) {
-            // This is normal because SystemUIApplication is an application context that is shared
-            // among multiple components
-            if (DEBUG) {
-                Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
-                        + " with that name (" + name + "): " + mDumpables.get(name));
-            }
-            return false;
-        }
-        if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable);
-        mDumpables.put(name, dumpable);
-
-        // TODO(b/217567642): replace com.android.systemui.dump.Dumpable by
-        // com.android.util.Dumpable and get rid of the intermediate lambda
-        mDumpManager.registerDumpable(dumpable.getDumpableName(), dumpable::dump);
-        return true;
-    }
-
-    // TODO(b/217567642): implement
-    @Override
-    public boolean removeDumpable(Dumpable dumpable) {
-        Log.w(TAG, "removeDumpable(" + dumpable + "): not implemented");
-
-        return false;
-    }
-
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 5499d2c..f04fdfff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -23,6 +23,7 @@
 import android.view.DisplayInfo
 import android.view.Surface
 import android.view.View
+import androidx.annotation.VisibleForTesting
 import com.airbnb.lottie.LottieAnimationView
 import com.android.settingslib.widget.LottieColorUtils
 import com.android.systemui.R
@@ -133,13 +134,19 @@
         }
     }
 
-    private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
+    @VisibleForTesting
+    fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
         val id = when (newState) {
             STATE_IDLE,
             STATE_AUTHENTICATING_ANIMATING_IN,
             STATE_AUTHENTICATING,
             STATE_PENDING_CONFIRMATION,
-            STATE_AUTHENTICATED -> R.string.security_settings_sfps_enroll_find_sensor_message
+            STATE_AUTHENTICATED ->
+                if (isSideFps) {
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                } else {
+                    R.string.fingerprint_dialog_touch_sensor
+                }
             STATE_ERROR,
             STATE_HELP -> R.string.biometric_dialog_try_again
             else -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index f330c34..e089fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -741,10 +741,10 @@
 
         mTitleView.setText(mPromptInfo.getTitle());
 
-        //setSelected could make marguee work
+        // setSelected could make marquee work
         mTitleView.setSelected(true);
         mSubtitleView.setSelected(true);
-        //make description view become scrollable
+        // make description view become scrollable
         mDescriptionView.setMovementMethod(new ScrollingMovementMethod());
 
         if (isDeviceCredentialAllowed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index b72801d..8edccf166 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -21,7 +21,7 @@
     fun enable(onPanelInteraction: Runnable) {
         if (action == null) {
             action = Action(onPanelInteraction)
-            shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+            shadeExpansionStateManager.addShadeExpansionListener(this::onPanelExpansionChanged)
         } else {
             Log.e(TAG, "Already enabled")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 92aff06..e600632 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -189,7 +189,7 @@
                     authorizedPanelsRepository.addAuthorizedPanels(
                             setOf(serviceInfo.componentName.packageName)
                     )
-                    val selected = SelectedItem.PanelItem(appName, componentName)
+                    val selected = SelectedItem.PanelItem(appName, serviceInfo.componentName)
                     controlsController.setPreferredSelection(selected)
                     animateExitAndFinish()
                     openControlsOrigin()
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8707c96..a4d4a9a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -206,11 +206,6 @@
                     "wallpaper_picker_ui_for_aiwp"
             )
 
-    /** Whether to inflate the bouncer view on a background thread. */
-    // TODO(b/273341787): Tracking Bug
-    @JvmField
-    val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard")
-
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
     @JvmField
     val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
@@ -608,6 +603,9 @@
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
     // TODO(b/278714186) Tracking Bug
     @JvmField val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout")
+    // TODO(b/279405451): Tracking Bug
+    @JvmField
+    val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions")
 
     // 1800 - shade container
     // TODO(b/265944639): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b5ddc2e..9e208fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard;
 
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
@@ -101,6 +102,7 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardStateCallback;
@@ -131,6 +133,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
@@ -1181,12 +1184,16 @@
     private Lazy<ScrimController> mScrimControllerLazy;
 
     private FeatureFlags mFeatureFlags;
+    private final UiEventLogger mUiEventLogger;
+    private final SessionTracker mSessionTracker;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
     public KeyguardViewMediator(
             Context context,
+            UiEventLogger uiEventLogger,
+            SessionTracker sessionTracker,
             UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
@@ -1270,6 +1277,8 @@
         mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
 
         mFeatureFlags = featureFlags;
+        mUiEventLogger = uiEventLogger;
+        mSessionTracker = sessionTracker;
     }
 
     public void userActivity() {
@@ -1660,6 +1669,13 @@
             if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
             notifyStartedWakingUp();
         }
+        mUiEventLogger.logWithInstanceIdAndPosition(
+                BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP,
+                0,
+                null,
+                mSessionTracker.getSessionId(SESSION_KEYGUARD),
+                pmWakeReason
+        );
         mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason);
         maybeSendUserPresentBroadcast();
         Trace.endSection();
@@ -2941,9 +2957,12 @@
         if (mSurfaceBehindRemoteAnimationFinishedCallback != null) {
             try {
                 mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished();
+            } catch (Throwable t) {
+                // The surface may no longer be available. Just capture the exception
+                Log.w(TAG, "Surface behind remote animation callback failed, and it's probably ok: "
+                        + t.getMessage());
+            } finally {
                 mSurfaceBehindRemoteAnimationFinishedCallback = null;
-            } catch (RemoteException e) {
-                e.printStackTrace();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 5e71458..deb8f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -21,6 +21,7 @@
 import android.os.PowerManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -50,6 +51,7 @@
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -63,12 +65,12 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
 
-import java.util.concurrent.Executor;
-
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
 /**
  * Dagger Module providing keyguard.
  */
@@ -93,6 +95,8 @@
     @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
+            UiEventLogger uiEventLogger,
+            SessionTracker sessionTracker,
             UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
@@ -124,6 +128,8 @@
             FeatureFlags featureFlags) {
         return new KeyguardViewMediator(
                 context,
+                uiEventLogger,
+                sessionTracker,
                 userTracker,
                 falsingCollector,
                 lockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
index 568db2f..e7803c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
@@ -31,7 +31,9 @@
         VibrationEffect.startComposition()
             .apply {
                 val vibrationDelayMs =
-                    (ShakeAnimationDuration.inWholeMilliseconds / ShakeAnimationCycles * 2).toInt()
+                    (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
+                    .toInt()
+
                 val vibrationCount = ShakeAnimationCycles.toInt() * 2
                 repeat(vibrationCount) {
                     addPrimitive(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index ad11360..3aa57dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -273,7 +273,7 @@
         val finger =
             LayoutInflater.from(context)
                 .inflate(
-                    R.layout.udfps_keyguard_view_internal,
+                    R.layout.udfps_keyguard_preview,
                     parentView,
                     false,
                 ) as View
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index f50a7a8..d949cf56f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -37,11 +37,13 @@
 import androidx.core.widget.CompoundButtonCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.R;
 
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Adapter for media output dialog.
@@ -52,6 +54,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f;
     private static final float DEVICE_CONNECTED_ALPHA = 1f;
+    protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
 
     public MediaOutputAdapter(MediaOutputController controller) {
         super(controller);
@@ -59,6 +62,13 @@
     }
 
     @Override
+    public void updateItems() {
+        mMediaItemList.clear();
+        mMediaItemList.addAll(mController.getMediaItemList());
+        notifyDataSetChanged();
+    }
+
+    @Override
     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
             int viewType) {
         super.onCreateViewHolder(viewGroup, viewType);
@@ -79,14 +89,14 @@
     @Override
     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
         if (mController.isAdvancedLayoutSupported()) {
-            if (position >= mController.getMediaItemList().size()) {
+            if (position >= mMediaItemList.size()) {
                 if (DEBUG) {
                     Log.d(TAG, "Incorrect position: " + position + " list size: "
-                            + mController.getMediaItemList().size());
+                            + mMediaItemList.size());
                 }
                 return;
             }
-            MediaItem currentMediaItem = mController.getMediaItemList().get(position);
+            MediaItem currentMediaItem = mMediaItemList.get(position);
             switch (currentMediaItem.getMediaItemType()) {
                 case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
                     ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
@@ -119,11 +129,11 @@
     @Override
     public long getItemId(int position) {
         if (mController.isAdvancedLayoutSupported()) {
-            if (position >= mController.getMediaItemList().size()) {
+            if (position >= mMediaItemList.size()) {
                 Log.d(TAG, "Incorrect position for item id: " + position);
                 return position;
             }
-            MediaItem currentMediaItem = mController.getMediaItemList().get(position);
+            MediaItem currentMediaItem = mMediaItemList.get(position);
             return currentMediaItem.getMediaDevice().isPresent()
                     ? currentMediaItem.getMediaDevice().get().getId().hashCode()
                     : position;
@@ -143,12 +153,12 @@
     @Override
     public int getItemViewType(int position) {
         if (mController.isAdvancedLayoutSupported()
-                && position >= mController.getMediaItemList().size()) {
+                && position >= mMediaItemList.size()) {
             Log.d(TAG, "Incorrect position for item type: " + position);
             return MediaItem.MediaItemType.TYPE_GROUP_DIVIDER;
         }
         return mController.isAdvancedLayoutSupported()
-                ? mController.getMediaItemList().get(position).getMediaItemType()
+                ? mMediaItemList.get(position).getMediaItemType()
                 : super.getItemViewType(position);
     }
 
@@ -156,7 +166,7 @@
     public int getItemCount() {
         // Add extra one for "pair new"
         return mController.isAdvancedLayoutSupported()
-                ? mController.getMediaItemList().size()
+                ? mMediaItemList.size()
                 : mController.getMediaDevices().size() + 1;
     }
 
@@ -482,6 +492,14 @@
         }
 
         private void onItemClick(View view, MediaDevice device) {
+            if (mController.isCurrentOutputDeviceHasSessionOngoing()) {
+                showCustomEndSessionDialog(device);
+            } else {
+                transferOutput(device);
+            }
+        }
+
+        private void transferOutput(MediaDevice device) {
             if (mController.isAnyDeviceTransferring()) {
                 return;
             }
@@ -496,6 +514,14 @@
             notifyDataSetChanged();
         }
 
+        @VisibleForTesting
+        void showCustomEndSessionDialog(MediaDevice device) {
+            MediaSessionReleaseDialog mediaSessionReleaseDialog = new MediaSessionReleaseDialog(
+                    mContext, () -> transferOutput(device), mController.getColorButtonBackground(),
+                    mController.getColorItemContent());
+            mediaSessionReleaseDialog.show();
+        }
+
         private void cancelMuteAwaitConnection() {
             mController.cancelMuteAwaitConnection();
             notifyDataSetChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 73ab5272..151dbb2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -81,6 +81,11 @@
         mIsInitVolumeFirstTime = true;
     }
 
+    /**
+     * Refresh current dataset
+     */
+    public abstract void updateItems();
+
     @Override
     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
             int viewType) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 08e47a0..770e4df 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -382,7 +382,7 @@
                     && currentActivePosition < mAdapter.getItemCount()) {
                 mAdapter.notifyItemChanged(currentActivePosition);
             } else {
-                mAdapter.notifyDataSetChanged();
+                mAdapter.updateItems();
             }
         } else {
             mMediaOutputController.setRefreshing(false);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 8e014c6..822644b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -783,6 +783,12 @@
                 currentConnectedMediaDevice);
     }
 
+    boolean isCurrentOutputDeviceHasSessionOngoing() {
+        MediaDevice currentConnectedMediaDevice = getCurrentConnectedMediaDevice();
+        return currentConnectedMediaDevice != null
+                && (currentConnectedMediaDevice.isHostForOngoingSession());
+    }
+
     public boolean isAdvancedLayoutSupported() {
         return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSessionReleaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSessionReleaseDialog.java
new file mode 100644
index 0000000..2680a2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSessionReleaseDialog.java
@@ -0,0 +1,89 @@
+/*
+ * 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.media.dialog;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+/**
+ * Confirmation dialog for releasing media session
+ */
+
+public class MediaSessionReleaseDialog extends SystemUIDialog {
+
+    private View mDialogView;
+
+    private final Context mContext;
+    private final View.OnClickListener mPositiveButtonListener;
+    private final ColorFilter mButtonColorFilter;
+    private final int mIconColor;
+
+    public MediaSessionReleaseDialog(Context context, Runnable runnable, int buttonColor,
+            int iconColor) {
+        super(context, R.style.Theme_SystemUI_Dialog_Media);
+        mContext = getContext();
+        mPositiveButtonListener = (v) -> {
+            runnable.run();
+            dismiss();
+        };
+        mButtonColorFilter = new PorterDuffColorFilter(
+                buttonColor,
+                PorterDuff.Mode.SRC_IN);
+        mIconColor = iconColor;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mDialogView = LayoutInflater.from(mContext).inflate(R.layout.media_session_end_dialog,
+                null);
+        final Window window = getWindow();
+        window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+        window.setContentView(mDialogView);
+
+        final WindowManager.LayoutParams lp = window.getAttributes();
+        lp.gravity = Gravity.CENTER;
+        lp.width = (int) (mContext.getResources().getDisplayMetrics().widthPixels * 0.90);
+
+        ImageView headerIcon = mDialogView.requireViewById(R.id.end_icon);
+        headerIcon.setImageDrawable(mContext.getDrawable(R.drawable.media_output_status_failed));
+        headerIcon.setImageTintList(
+                ColorStateList.valueOf(mIconColor));
+
+        Button stopButton = mDialogView.requireViewById(R.id.stop_button);
+        stopButton.setOnClickListener(mPositiveButtonListener);
+        stopButton.getBackground().setColorFilter(mButtonColorFilter);
+
+        Button cancelButton = mDialogView.requireViewById(R.id.cancel_button);
+        cancelButton.setOnClickListener((v) -> dismiss());
+        cancelButton.getBackground().setColorFilter(mButtonColorFilter);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a43f520..07259c2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -205,8 +205,11 @@
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
-                        centralSurfaces.getShadeViewController()
-                                        .startExpandLatencyTracking();
+                        ShadeViewController shadeViewController =
+                                centralSurfaces.getShadeViewController();
+                        if (shadeViewController != null) {
+                            shadeViewController.startExpandLatencyTracking();
+                        }
                     }
                     mHandler.post(() -> {
                         int action = event.getActionMasked();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 2312c70..4bc7ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -343,22 +343,24 @@
         } else {
             String editorPackage = getString(R.string.config_screenshotEditor);
             Intent intent = new Intent(Intent.ACTION_EDIT);
-            if (!TextUtils.isEmpty(editorPackage)) {
-                intent.setComponent(ComponentName.unflattenFromString(editorPackage));
-            }
             intent.setDataAndType(uri, "image/png");
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            Bundle options = null;
 
-            mTransitionView.setImageBitmap(mOutputBitmap);
-            mTransitionView.setVisibility(View.VISIBLE);
-            mTransitionView.setTransitionName(
-                    ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
-            // TODO: listen for transition completing instead of finishing onStop
-            mTransitionStarted = true;
-            startActivity(intent,
-                    ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
-                            ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+            // Skip shared element transition for implicit edit intents
+            if (!TextUtils.isEmpty(editorPackage)) {
+                intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+                mTransitionView.setImageBitmap(mOutputBitmap);
+                mTransitionView.setVisibility(View.VISIBLE);
+                mTransitionView.setTransitionName(
+                        ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+                options = ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
+                        ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle();
+                // TODO: listen for transition completing instead of finishing onStop
+                mTransitionStarted = true;
+            }
+            startActivity(intent, options);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 860bfe37..13678b0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -121,13 +121,15 @@
         LinearLayout.LayoutParams textParams =
                 (LinearLayout.LayoutParams) mTextView.getLayoutParams();
         if (hasText) {
-            int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.overlay_action_chip_padding_horizontal);
+            int paddingStart = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.overlay_action_chip_padding_start);
             int spacing = mContext.getResources().getDimensionPixelSize(
                     R.dimen.overlay_action_chip_spacing);
-            iconParams.setMarginStart(paddingHorizontal);
+            int paddingEnd = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.overlay_action_chip_padding_end);
+            iconParams.setMarginStart(paddingStart);
             iconParams.setMarginEnd(spacing);
-            textParams.setMarginEnd(paddingHorizontal);
+            textParams.setMarginEnd(paddingEnd);
         } else {
             int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
                     R.dimen.overlay_action_chip_icon_only_padding_horizontal);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index af74c27..46f1210 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -423,12 +423,18 @@
     }
 
     private boolean isExpanded(NotificationShadeWindowState state) {
-        return !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
+        boolean isExpanded = !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
                 || state.headsUpNotificationShowing
                 || state.scrimsVisibility != ScrimController.TRANSPARENT)
                 || state.backgroundBlurRadius > 0
                 || state.launchingActivityFromNotification;
+        mLogger.logIsExpanded(isExpanded, state.forceWindowCollapsed,
+                state.isKeyguardShowingAndNotOccluded(), state.panelVisible,
+                state.keyguardFadingAway, state.bouncerShowing, state.headsUpNotificationShowing,
+                state.scrimsVisibility != ScrimController.TRANSPARENT,
+                state.backgroundBlurRadius > 0, state.launchingActivityFromNotification);
+        return isExpanded;
     }
 
     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 20313c3..a048f54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -54,12 +54,20 @@
      * Listener will also be immediately notified with the current values.
      */
     fun addExpansionListener(listener: ShadeExpansionListener) {
-        expansionListeners.add(listener)
+        addShadeExpansionListener(listener)
         listener.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
         )
     }
 
+    /**
+     * Adds a listener that will be notified when the panel expansion fraction has changed.
+     * @see #addExpansionListener
+     */
+    fun addShadeExpansionListener(listener: ShadeExpansionListener) {
+        expansionListeners.add(listener)
+    }
+
     /** Removes an expansion listener. */
     fun removeExpansionListener(listener: ShadeExpansionListener) {
         expansionListeners.remove(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index d8d4279..d06634b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -65,6 +65,41 @@
             { "Updating visibility, should be visible : $bool1" })
     }
 
+    fun logIsExpanded(
+        isExpanded: Boolean,
+        forceWindowCollapsed: Boolean,
+        isKeyguardShowingAndNotOccluded: Boolean,
+        panelVisible: Boolean,
+        keyguardFadingAway: Boolean,
+        bouncerShowing: Boolean,
+        headsUpNotificationShowing: Boolean,
+        scrimsVisibilityNotTransparent: Boolean,
+        backgroundBlurRadius: Boolean,
+        launchingActivityFromNotification: Boolean
+    ) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = isExpanded.toString()
+                bool1 = forceWindowCollapsed
+                bool2 = isKeyguardShowingAndNotOccluded
+                bool3 = panelVisible
+                bool4 = keyguardFadingAway
+                int1 = if (bouncerShowing) 1 else 0
+                int2 = if (headsUpNotificationShowing) 1 else 0
+                long1 = if (scrimsVisibilityNotTransparent) 1 else 0
+                long2 = if (backgroundBlurRadius) 1 else 0
+                double1 = if (launchingActivityFromNotification) 1.0 else 0.0
+            },
+            { "Setting isExpanded to $str1: forceWindowCollapsed $bool1, " +
+                    "isKeyguardShowingAndNotOccluded $bool2, panelVisible $bool3, " +
+                    "keyguardFadingAway $bool4, bouncerShowing $int1," +
+                    "headsUpNotificationShowing $int2, scrimsVisibilityNotTransparent $long1," +
+                    "backgroundBlurRadius $long2, launchingActivityFromNotification $double1"}
+        )
+    }
+
     fun logShadeVisibleAndFocusable(visible: Boolean) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index 12f2c22..f84b96c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -15,13 +15,14 @@
  */
 package com.android.systemui.statusbar.connectivity;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
 
 import android.content.Context;
 import android.content.Intent;
-import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.text.Html;
@@ -37,6 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 
 import java.io.PrintWriter;
+import java.util.BitSet;
 
 /** */
 public class WifiSignalController extends SignalController<WifiState, IconGroup> {
@@ -56,8 +58,12 @@
             WifiManager wifiManager,
             WifiStatusTrackerFactory trackerFactory,
             @Background Handler bgHandler) {
-        super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
-                callbackHandler, networkController);
+        super(
+                "WifiSignalController",
+                context,
+                TRANSPORT_WIFI,
+                callbackHandler,
+                networkController);
         mBgHandler = bgHandler;
         mWifiManager = wifiManager;
         mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler);
@@ -160,7 +166,10 @@
         // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so
         // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count.
         int totalLevel = mWifiManager.getMaxSignalLevel() + 1;
-        boolean noInternet = mCurrentState.inetCondition == 0;
+        // A carrier merged connection could come from a WIFI *or* CELLULAR transport, so we can't
+        // use [mCurrentState.inetCondition], which only checks the WIFI status. Instead, check if
+        // the default connection is validated at all.
+        boolean noInternet = !mCurrentState.isDefaultConnectionValidated;
         if (mCurrentState.connected) {
             return SignalDrawable.getState(level, totalLevel, noInternet);
         } else if (mCurrentState.enabled) {
@@ -236,6 +245,18 @@
                 && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId);
     }
 
+    @Override
+    void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+        mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
+        // Because a carrier merged connection can come from either a CELLULAR *or* WIFI transport,
+        // we need to also store if either transport is validated to correctly display the carrier
+        // merged case.
+        mCurrentState.isDefaultConnectionValidated =
+                validatedTransports.get(TRANSPORT_CELLULAR)
+                        || validatedTransports.get(TRANSPORT_WIFI);
+        notifyListenersIfNecessary();
+    }
+
     @VisibleForTesting
     void setActivity(int wifiActivity) {
         mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
index d32e349..63a63de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
@@ -24,6 +24,14 @@
     @JvmField var isDefault: Boolean = false,
     @JvmField var statusLabel: String? = null,
     @JvmField var isCarrierMerged: Boolean = false,
+    /**
+     * True if the current default connection is validated for *any* transport, not just wifi.
+     * (Specifically TRANSPORT_CELLULAR *or* TRANSPORT_WIFI.)
+     *
+     * This should *only* be used when calculating information for the carrier merged connection and
+     * *not* for typical wifi connections. See b/225902574.
+     */
+    @JvmField var isDefaultConnectionValidated: Boolean = false,
     @JvmField var subId: Int = 0
 ) : ConnectivityState() {
 
@@ -35,6 +43,7 @@
         isDefault = state.isDefault
         statusLabel = state.statusLabel
         isCarrierMerged = state.isCarrierMerged
+        isDefaultConnectionValidated = state.isDefaultConnectionValidated
         subId = state.subId
     }
 
@@ -45,6 +54,7 @@
                 .append(",isDefault=").append(isDefault)
                 .append(",statusLabel=").append(statusLabel)
                 .append(",isCarrierMerged=").append(isCarrierMerged)
+                .append(",isDefaultConnectionValidated=").append(isDefaultConnectionValidated)
                 .append(",subId=").append(subId)
     }
 
@@ -54,6 +64,7 @@
                 "isDefault",
                 "statusLabel",
                 "isCarrierMerged",
+                "isDefaultConnectionValidated",
                 "subId")
 
         return super.tableColumns() + columns
@@ -65,6 +76,7 @@
         isDefault,
         statusLabel,
         isCarrierMerged,
+        isDefaultConnectionValidated,
         subId).map {
             it.toString()
         }
@@ -84,6 +96,7 @@
         if (isDefault != other.isDefault) return false
         if (statusLabel != other.statusLabel) return false
         if (isCarrierMerged != other.isCarrierMerged) return false
+        if (isDefaultConnectionValidated != other.isDefaultConnectionValidated) return false
         if (subId != other.subId) return false
 
         return true
@@ -96,6 +109,7 @@
         result = 31 * result + isDefault.hashCode()
         result = 31 * result + (statusLabel?.hashCode() ?: 0)
         result = 31 * result + isCarrierMerged.hashCode()
+        result = 31 * result + isDefaultConnectionValidated.hashCode()
         result = 31 * result + subId
         return result
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
index 92a8356..1aeb6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
@@ -22,7 +22,6 @@
 import android.view.Choreographer
 import android.view.InputEvent
 import android.view.MotionEvent
-import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.shared.system.InputChannelCompat
 import com.android.systemui.shared.system.InputMonitorCompat
 
@@ -39,7 +38,7 @@
  */
 abstract class GenericGestureDetector(
     private val tag: String,
-    private val displayTracker: DisplayTracker
+    private val displayId: Int,
 ) {
     /**
      * Active callbacks, each associated with a tag. Gestures will only be monitored if
@@ -87,7 +86,7 @@
     internal open fun startGestureListening() {
         stopGestureListening()
 
-        inputMonitor = InputMonitorCompat(tag, displayTracker.defaultDisplayId).also {
+        inputMonitor = InputMonitorCompat(tag, displayId).also {
             inputReceiver = it.getInputReceiver(
                 Looper.getMainLooper(),
                 Choreographer.getInstance(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
index 6d60f4a9..2fd0a53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
@@ -36,7 +36,10 @@
     displayTracker: DisplayTracker,
     private val logger: SwipeUpGestureLogger,
     private val loggerTag: String,
-) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!, displayTracker) {
+) : GenericGestureDetector(
+        SwipeUpGestureHandler::class.simpleName!!,
+        displayTracker.defaultDisplayId
+) {
 
     private var startY: Float = 0f
     private var startTime: Long = 0L
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
index a901d597..ed30f2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
@@ -32,7 +32,10 @@
 class TapGestureDetector @Inject constructor(
     private val context: Context,
     displayTracker: DisplayTracker
-) : GenericGestureDetector(TapGestureDetector::class.simpleName!!, displayTracker) {
+) : GenericGestureDetector(
+        TapGestureDetector::class.simpleName!!,
+        displayTracker.defaultDisplayId
+) {
 
     private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
         override fun onSingleTapUp(e: MotionEvent): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6742e4f..bd7840d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -190,7 +190,6 @@
         }
     }
 
-    @VisibleForTesting
     public enum BiometricUiEvent implements UiEventLogger.UiEventEnum {
 
         @UiEvent(doc = "A biometric event of type fingerprint succeeded.")
@@ -221,7 +220,10 @@
         BIOMETRIC_IRIS_ERROR(404),
 
         @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.")
-        BIOMETRIC_BOUNCER_SHOWN(916);
+        BIOMETRIC_BOUNCER_SHOWN(916),
+
+        @UiEvent(doc = "Screen started waking up with the given PowerManager wake reason.")
+        STARTED_WAKING_UP(1378);
 
         private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 560ea8a..313410a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -604,6 +604,7 @@
         }
         updateAodIconsVisibility(animate, false /* force */);
         updateAodNotificationIcons();
+        updateAodIconColors();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index bd5815aa..7bbb03b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -304,10 +304,11 @@
             ActivityManager.getService().resumeAppSwitches();
         } catch (RemoteException e) {
         }
-        // If we are launching a work activity and require to launch
-        // separate work challenge, we defer the activity action and cancel
-        // notification until work challenge is unlocked.
-        if (isActivityIntent) {
+        // If the notification should be cancelled on click and we are launching a work activity in
+        // a locked profile with separate challenge, we defer the activity action and cancelling of
+        // the notification until work challenge is unlocked. If the notification shouldn't be
+        // cancelled, the work challenge will be shown by ActivityManager if necessary anyway.
+        if (isActivityIntent && shouldAutoCancel(entry.getSbn())) {
             final int userId = intent.getCreatorUserHandle().getIdentifier();
             if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
                     && mKeyguardManager.isDeviceLocked(userId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 7aa9033..49de5a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -160,7 +160,7 @@
         @SysUISingleton
         @SharedConnectivityInputLog
         fun provideSharedConnectivityTableLogBuffer(factory: LogBufferFactory): LogBuffer {
-            return factory.create("SharedConnectivityInputLog", 30)
+            return factory.create("SharedConnectivityInputLog", 60)
         }
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 0e9b6c5..81a068d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -88,6 +89,7 @@
     private val context: Context,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
+    airplaneModeRepository: AirplaneModeRepository,
     // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
     // repository is an input to the mobile repository.
     // See [CarrierMergedConnectionRepository] for details.
@@ -106,10 +108,20 @@
         context.getString(R.string.status_bar_network_name_separator)
 
     private val carrierMergedSubId: StateFlow<Int?> =
-        wifiRepository.wifiNetwork
-            .mapLatest {
-                if (it is WifiNetworkModel.CarrierMerged) {
-                    it.subscriptionId
+        combine(
+                wifiRepository.wifiNetwork,
+                connectivityRepository.defaultConnections,
+                airplaneModeRepository.isAirplaneMode,
+            ) { wifiNetwork, defaultConnections, isAirplaneMode ->
+                // The carrier merged connection should only be used if it's also the default
+                // connection or mobile connections aren't available because of airplane mode.
+                val defaultConnectionIsNonMobile =
+                    defaultConnections.carrierMerged.isDefault ||
+                        defaultConnections.wifi.isDefault ||
+                        isAirplaneMode
+
+                if (wifiNetwork is WifiNetworkModel.CarrierMerged && defaultConnectionIsNonMobile) {
+                    wifiNetwork.subscriptionId
                 } else {
                     null
                 }
@@ -269,12 +281,8 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val hasCarrierMergedConnection: StateFlow<Boolean> =
-        combine(
-                connectivityRepository.defaultConnections,
-                carrierMergedSubId,
-            ) { defaultConnections, carrierMergedSubId ->
-                defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null
-            }
+        carrierMergedSubId
+            .map { it != null }
             .distinctUntilChanged()
             .logDiffsForTable(
                 tableLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index eec91a0..e90f40c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -155,7 +155,8 @@
         combine(
                 unfilteredSubscriptions,
                 mobileConnectionsRepo.activeMobileDataSubscriptionId,
-            ) { unfilteredSubs, activeId ->
+                connectivityRepository.vcnSubId,
+            ) { unfilteredSubs, activeId, vcnSubId ->
                 // Based on the old logic,
                 if (unfilteredSubs.size != 2) {
                     return@combine unfilteredSubs
@@ -182,7 +183,13 @@
                     // return the non-opportunistic info
                     return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
                 } else {
-                    return@combine if (info1.subscriptionId == activeId) {
+                    // It's possible for the subId of the VCN to disagree with the active subId in
+                    // cases where the system has tried to switch but found no connection. In these
+                    // scenarios, VCN will always have the subId that we want to use, so use that
+                    // value instead of the activeId reported by telephony
+                    val subIdToKeep = vcnSubId ?: activeId
+
+                    return@combine if (info1.subscriptionId == subIdToKeep) {
                         listOf(info1)
                     } else {
                         listOf(info2)
@@ -259,7 +266,7 @@
      */
     override val isDefaultConnectionFailed: StateFlow<Boolean> =
         combine(
-                mobileConnectionsRepo.mobileIsDefault,
+                mobileIsDefault,
                 mobileConnectionsRepo.defaultConnectionIsValidated,
                 forcingCellularValidation,
             ) { mobileIsDefault, defaultConnectionIsValidated, forcingCellularValidation ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index 051f43f..cac0ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -61,6 +61,10 @@
             model::messagePrinter,
         )
     }
+
+    fun logVcnSubscriptionId(subId: Int) {
+        buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" })
+    }
 }
 
 private const val TAG = "ConnectivityInputLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 731f1e0..7076f34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -27,6 +27,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.annotation.ArrayRes
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
@@ -50,10 +51,13 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -66,6 +70,16 @@
 
     /** Observable for which connection(s) are currently default. */
     val defaultConnections: StateFlow<DefaultConnectionModel>
+
+    /**
+     * Subscription ID of the [VcnTransportInfo] for the default connection.
+     *
+     * If the default network has a [VcnTransportInfo], then that transport info contains a subId of
+     * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In
+     * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to
+     * eventually catch up and reflect what is represented here in the VcnTransportInfo.
+     */
+    val vcnSubId: StateFlow<Int?>
 }
 
 @SuppressLint("MissingPermission")
@@ -118,24 +132,13 @@
                 initialValue = defaultHiddenIcons
             )
 
-    @SuppressLint("MissingPermission")
-    override val defaultConnections: StateFlow<DefaultConnectionModel> =
+    private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> =
         conflatedCallbackFlow {
                 val callback =
                     object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                         override fun onLost(network: Network) {
                             logger.logOnDefaultLost(network)
-                            // The system no longer has a default network, so everything is
-                            // non-default.
-                            trySend(
-                                DefaultConnectionModel(
-                                    Wifi(isDefault = false),
-                                    Mobile(isDefault = false),
-                                    CarrierMerged(isDefault = false),
-                                    Ethernet(isDefault = false),
-                                    isValidated = false,
-                                )
-                            )
+                            trySend(null)
                         }
 
                         override fun onCapabilitiesChanged(
@@ -143,30 +146,7 @@
                             networkCapabilities: NetworkCapabilities,
                         ) {
                             logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
-
-                            val wifiInfo =
-                                networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
-
-                            val isWifiDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
-                            val isMobileDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
-                            val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
-                            val isEthernetDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
-
-                            val isValidated =
-                                networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
-
-                            trySend(
-                                DefaultConnectionModel(
-                                    Wifi(isWifiDefault),
-                                    Mobile(isMobileDefault),
-                                    CarrierMerged(isCarrierMergedDefault),
-                                    Ethernet(isEthernetDefault),
-                                    isValidated,
-                                )
-                            )
+                            trySend(networkCapabilities)
                         }
                     }
 
@@ -174,6 +154,61 @@
 
                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
             }
+            .shareIn(scope, SharingStarted.WhileSubscribed())
+
+    override val vcnSubId: StateFlow<Int?> =
+        defaultNetworkCapabilities
+            .map { networkCapabilities ->
+                networkCapabilities?.run {
+                    val subId = (transportInfo as? VcnTransportInfo)?.subId
+                    // Never return an INVALID_SUBSCRIPTION_ID (-1)
+                    if (subId != INVALID_SUBSCRIPTION_ID) {
+                        subId
+                    } else {
+                        null
+                    }
+                }
+            }
+            .distinctUntilChanged()
+            /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */
+            .onEach { logger.logVcnSubscriptionId(it ?: -2) }
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
+    @SuppressLint("MissingPermission")
+    override val defaultConnections: StateFlow<DefaultConnectionModel> =
+        defaultNetworkCapabilities
+            .map { networkCapabilities ->
+                if (networkCapabilities == null) {
+                    // The system no longer has a default network, so everything is
+                    // non-default.
+                    DefaultConnectionModel(
+                        Wifi(isDefault = false),
+                        Mobile(isDefault = false),
+                        CarrierMerged(isDefault = false),
+                        Ethernet(isDefault = false),
+                        isValidated = false,
+                    )
+                } else {
+                    val wifiInfo =
+                        networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+                    val isWifiDefault =
+                        networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
+                    val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+                    val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
+                    val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
+
+                    val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
+
+                    DefaultConnectionModel(
+                        Wifi(isWifiDefault),
+                        Mobile(isMobileDefault),
+                        CarrierMerged(isCarrierMergedDefault),
+                        Ethernet(isEthernetDefault),
+                        isValidated,
+                    )
+                }
+            }
             .distinctUntilChanged()
             .onEach { logger.logDefaultConnectionsChanged(it) }
             .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 5208064..5cc3d52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -38,7 +38,11 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
+import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -50,14 +54,20 @@
 import javax.inject.Inject;
 
 /**
+ * Controller for information about bluetooth connections.
+ *
+ * Note: Right now, this class and {@link BluetoothRepository} co-exist. Any new code should go in
+ * {@link BluetoothRepository}, but external clients should query this file for now.
  */
 @SysUISingleton
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
 
+    private final FeatureFlags mFeatureFlags;
     private final DumpManager mDumpManager;
     private final BluetoothLogger mLogger;
+    private final BluetoothRepository mBluetoothRepository;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
@@ -79,14 +89,18 @@
     @Inject
     public BluetoothControllerImpl(
             Context context,
+            FeatureFlags featureFlags,
             UserTracker userTracker,
             DumpManager dumpManager,
             BluetoothLogger logger,
+            BluetoothRepository bluetoothRepository,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Nullable BluetoothAdapter bluetoothAdapter) {
+        mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
         mLogger = logger;
+        mBluetoothRepository = bluetoothRepository;
         mLocalBluetoothManager = localBluetoothManager;
         mHandler = new H(mainLooper);
         if (mLocalBluetoothManager != null) {
@@ -229,6 +243,16 @@
     }
 
     private void updateConnected() {
+        if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) {
+            mBluetoothRepository.fetchConnectionStatusInBackground(
+                    getDevices(), this::onConnectionStatusFetched);
+        } else {
+            updateConnectedOld();
+        }
+    }
+
+    /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */
+    private void updateConnectedOld() {
         // Make sure our connection state is up to date.
         int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
         List<CachedBluetoothDevice> newList = new ArrayList<>();
@@ -249,6 +273,12 @@
             // connected.
             state = BluetoothAdapter.STATE_DISCONNECTED;
         }
+        onConnectionStatusFetched(new ConnectionStatusModel(state, newList));
+    }
+
+    private void onConnectionStatusFetched(ConnectionStatusModel status) {
+        List<CachedBluetoothDevice> newList = status.getConnectedDevices();
+        int state = status.getMaxConnectionState();
         synchronized (mConnectedDevices) {
             mConnectedDevices.clear();
             mConnectedDevices.addAll(newList);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
new file mode 100644
index 0000000..80f3d76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothProfile
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for information about bluetooth connections.
+ *
+ * Note: Right now, this class and [BluetoothController] co-exist. Any new code should go in this
+ * implementation, but external clients should query [BluetoothController] instead of this class for
+ * now.
+ */
+interface BluetoothRepository {
+    /**
+     * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once
+     * those statuses have been fetched. The fetching occurs on a background thread because IPCs may
+     * be required to fetch the statuses (see b/271058380).
+     */
+    fun fetchConnectionStatusInBackground(
+        currentDevices: Collection<CachedBluetoothDevice>,
+        callback: ConnectionStatusFetchedCallback,
+    )
+}
+
+/** Implementation of [BluetoothRepository]. */
+@SysUISingleton
+class BluetoothRepositoryImpl
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val localBluetoothManager: LocalBluetoothManager?,
+) : BluetoothRepository {
+    override fun fetchConnectionStatusInBackground(
+        currentDevices: Collection<CachedBluetoothDevice>,
+        callback: ConnectionStatusFetchedCallback,
+    ) {
+        scope.launch {
+            val result = fetchConnectionStatus(currentDevices)
+            callback.onConnectionStatusFetched(result)
+        }
+    }
+
+    private suspend fun fetchConnectionStatus(
+        currentDevices: Collection<CachedBluetoothDevice>,
+    ): ConnectionStatusModel {
+        return withContext(bgDispatcher) {
+            val minimumMaxConnectionState =
+                localBluetoothManager?.bluetoothAdapter?.connectionState
+                    ?: BluetoothProfile.STATE_DISCONNECTED
+            var maxConnectionState =
+                if (currentDevices.isEmpty()) {
+                    minimumMaxConnectionState
+                } else {
+                    currentDevices
+                        .maxOf { it.maxConnectionState }
+                        .coerceAtLeast(minimumMaxConnectionState)
+                }
+
+            val connectedDevices = currentDevices.filter { it.isConnected }
+
+            if (
+                connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED
+            ) {
+                // If somehow we think we are connected, but have no connected devices, we aren't
+                // connected.
+                maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED
+            }
+
+            ConnectionStatusModel(maxConnectionState, connectedDevices)
+        }
+    }
+}
+
+data class ConnectionStatusModel(
+    /** The maximum connection state out of all current devices. */
+    val maxConnectionState: Int,
+    /** A list of devices that are currently connected. */
+    val connectedDevices: List<CachedBluetoothDevice>,
+)
+
+/** Callback notified when the new status has been fetched. */
+fun interface ConnectionStatusFetchedCallback {
+    fun onConnectionStatusFetched(status: ConnectionStatusModel)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 1b73539..e1a7b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -62,15 +62,16 @@
 import com.android.systemui.statusbar.policy.WalletControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
+import javax.inject.Named;
 
 /** Dagger Module for code in the statusbar.policy package. */
 @Module
@@ -84,6 +85,10 @@
 
     /** */
     @Binds
+    BluetoothRepository provideBluetoothRepository(BluetoothRepositoryImpl impl);
+
+    /** */
+    @Binds
     CastController provideCastController(CastControllerImpl controllerImpl);
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index d74906a..eed7950 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -154,10 +154,6 @@
                 pendingScrimReadyCallback = onReady
             }
         } else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) {
-            // Screen turning on for the first time after folding and we are already dozing
-            // We should play the folding to AOD animation
-            isFoldHandled = true
-
             setAnimationState(playing = true)
             getShadeFoldAnimator().prepareFoldToAodAnimation()
 
@@ -173,6 +169,13 @@
             // No animation, call ready callback immediately
             onReady.run()
         }
+
+        if (isFolded) {
+            // Any time the screen turns on, this state needs to be reset if the device has been
+            // folded. Reaching this line implies AOD has been shown in one way or another,
+            // if enabled
+            isFoldHandled = true
+        }
     }
 
     /** Called when keyguard scrim opaque changed */
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index cd1ad1b..316b54e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -173,7 +173,7 @@
                     .isLockscreenLiveWallpaperEnabled();
             mSurfaceHolder = surfaceHolder;
             Rect dimensions = mIsLockscreenLiveWallpaperEnabled
-                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag())
+                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
                     : mWallpaperManager.peekBitmapDimensions();
             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
@@ -325,7 +325,7 @@
             try {
                 bitmap = mIsLockscreenLiveWallpaperEnabled
                         ? mWallpaperManager.getBitmapAsUser(
-                                mUserTracker.getUserId(), false, getSourceFlag())
+                                mUserTracker.getUserId(), false, getSourceFlag(), true)
                         : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
                 if (bitmap != null
                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
@@ -347,7 +347,7 @@
                 try {
                     bitmap = mIsLockscreenLiveWallpaperEnabled
                             ? mWallpaperManager.getBitmapAsUser(
-                                    mUserTracker.getUserId(), false, getSourceFlag())
+                                    mUserTracker.getUserId(), false, getSourceFlag(), true)
                             : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index fb73845..d8e2a38 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -134,6 +134,7 @@
 
     private KeyguardClockSwitchController mController;
     private View mSliceView;
+    private LinearLayout mStatusArea;
     private FakeExecutor mExecutor;
 
     @Before
@@ -195,8 +196,8 @@
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
-        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
-                new LinearLayout(getContext()));
+        mStatusArea = new LinearLayout(getContext());
+        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
     }
 
     @Test
@@ -401,6 +402,15 @@
         assertNull(mController.getClock());
     }
 
+    @Test
+    public void testSetAlpha_setClockAlphaForCLockFace() {
+        mController.onViewAttached();
+        mController.setAlpha(0.5f);
+        verify(mLargeClockView).setAlpha(0.5f);
+        verify(mSmallClockView).setAlpha(0.5f);
+        assertEquals(0.5f, mStatusArea.getAlpha(), 0.0f);
+    }
+
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 8a05a37..65ddb53 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -64,7 +64,6 @@
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
@@ -398,26 +397,6 @@
     }
 
     @Test
-    public void showNextSecurityScreenOrFinish_DeviceNotSecure_prevent_bypass_on() {
-        when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true);
-        // GIVEN the current security method is SimPin
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
-        mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        // THEN the next security method of None will dismiss keyguard.
-        verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
-    }
-
-    @Test
     public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
         //GIVEN current security mode has been set to PIN
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
@@ -608,7 +587,6 @@
 
     @Test
     public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() {
-        when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true);
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
         mKeyguardSecurityContainerController.finish(false, 0);
         verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 508aea5..a8c281c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -24,6 +24,8 @@
         get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
     private val statusViewContainer: ViewGroup
         get() = keyguardStatusView.findViewById(R.id.status_view_container)
+    private val clockView: ViewGroup
+        get() = keyguardStatusView.findViewById(R.id.keyguard_clock_container)
     private val childrenExcludingMedia
         get() = statusViewContainer.children.filter { it != mediaView }
 
@@ -56,4 +58,12 @@
             assertThat(it.translationY).isEqualTo(translationY)
         }
     }
+
+    @Test
+    fun setAlphaExcludeClock() {
+        keyguardStatusView.setAlpha(0.5f, /* excludeClock= */true)
+        assertThat(statusViewContainer.alpha).isNotEqualTo(0.5f)
+        assertThat(mediaView.alpha).isEqualTo(0.5f)
+        assertThat(clockView.alpha).isNotEqualTo(0.5f)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
new file mode 100644
index 0000000..cac618b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
@@ -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.systemui.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.SensorProperties
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.view.ViewGroup.LayoutParams
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+private const val SENSOR_ID = 1
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var iconView: LottieAnimationView
+    @Mock private lateinit var iconViewOverlay: LottieAnimationView
+    @Mock private lateinit var layoutParam: LayoutParams
+    @Mock private lateinit var fingerprintManager: FingerprintManager
+
+    private lateinit var controller: AuthBiometricFingerprintIconController
+
+    @Before
+    fun setUp() {
+        context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
+        whenEver(iconView.layoutParams).thenReturn(layoutParam)
+        whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
+    }
+
+    @Test
+    fun testIconContentDescription_SfpsDevice() {
+        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
+        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
+
+        assertThat(controller.getIconContentDescription(AuthBiometricView.STATE_AUTHENTICATING))
+            .isEqualTo(
+                context.resources.getString(
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                )
+            )
+    }
+
+    @Test
+    fun testIconContentDescription_NonSfpsDevice() {
+        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
+
+        assertThat(controller.getIconContentDescription(AuthBiometricView.STATE_AUTHENTICATING))
+            .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
+    }
+
+    private fun setupFingerprintSensorProperties(sensorType: Int) {
+        whenEver(fingerprintManager.sensorPropertiesInternal)
+            .thenReturn(
+                listOf(
+                    FingerprintSensorPropertiesInternal(
+                        SENSOR_ID,
+                        SensorProperties.STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        listOf() /* componentInfo */,
+                        sensorType,
+                        true /* halControlsIllumination */,
+                        true /* resetLockoutRequiresHardwareAuthToken */,
+                        listOf() /* sensorLocations */
+                    )
+                )
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 8dfd223..b2e37cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
@@ -190,6 +191,9 @@
         val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor()
         verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor))
         assertThat(setCaptor.value).containsExactly(info.componentName.packageName)
+        val selectedComponentCaptor: ArgumentCaptor<SelectedItem> = argumentCaptor()
+        verify(controlsController).setPreferredSelection(capture(selectedComponentCaptor))
+        assertThat(selectedComponentCaptor.value.componentName).isEqualTo(info.componentName)
 
         assertThat(activityRule.activity.triedToFinish).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8f58140..d4c2baf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -52,6 +52,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardSecurityView;
@@ -68,6 +70,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -77,6 +80,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -143,6 +147,8 @@
     private FalsingCollectorFake mFalsingCollector;
 
     private @Mock CentralSurfaces mCentralSurfaces;
+    private @Mock UiEventLogger mUiEventLogger;
+    private @Mock SessionTracker mSessionTracker;
 
     private FakeFeatureFlags mFeatureFlags;
 
@@ -543,9 +549,26 @@
         assertTrue(mViewMediator.isShowingAndNotOccluded());
     }
 
+    @Test
+    public void testOnStartedWakingUp_logsUiEvent() {
+        final InstanceId instanceId = InstanceId.fakeInstanceId(8);
+        when(mSessionTracker.getSessionId((anyInt()))).thenReturn(instanceId);
+        mViewMediator.onStartedWakingUp(PowerManager.WAKE_REASON_LIFT, false);
+
+        verify(mUiEventLogger).logWithInstanceIdAndPosition(
+                eq(BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP),
+                anyInt(),
+                any(),
+                eq(instanceId),
+                eq(PowerManager.WAKE_REASON_LIFT)
+        );
+    }
+
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
+                mUiEventLogger,
+                mSessionTracker,
                 mUserTracker,
                 mFalsingCollector,
                 mLockPatternUtils,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7f7952f..faca8a91d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -26,6 +26,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -34,6 +35,7 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
@@ -60,6 +62,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class MediaOutputAdapterTest extends SysuiTestCase {
 
     private static final String TEST_DEVICE_NAME_1 = "test_device_name_1";
@@ -116,6 +119,7 @@
         mMediaItems.add(new MediaItem(mMediaDevice2));
 
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mMediaOutputAdapter.updateItems();
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
                 .onCreateViewHolder(new LinearLayout(mContext), 0);
         mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
@@ -202,9 +206,11 @@
     public void advanced_onBindViewHolder_bindPairNew_verifyView() {
         when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mMediaOutputAdapter.updateItems();
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
                 .onCreateViewHolder(new LinearLayout(mContext), 0);
         mMediaItems.add(new MediaItem());
+        mMediaOutputAdapter.updateItems();
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
 
         assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
@@ -223,6 +229,7 @@
                         Collectors.toList()));
         when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mMediaOutputAdapter.updateItems();
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
                 .onCreateViewHolder(new LinearLayout(mContext), 0);
         mMediaOutputAdapter.getItemCount();
@@ -243,6 +250,7 @@
                         Collectors.toList()));
         when(mMediaOutputController.getSessionName()).thenReturn(null);
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mMediaOutputAdapter.updateItems();
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
                 .onCreateViewHolder(new LinearLayout(mContext), 0);
         mMediaOutputAdapter.getItemCount();
@@ -602,9 +610,11 @@
     public void advanced_onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
         when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mMediaOutputAdapter.updateItems();
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
                 .onCreateViewHolder(new LinearLayout(mContext), 0);
         mMediaItems.add(new MediaItem());
+        mMediaOutputAdapter.updateItems();
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
         mViewHolder.mContainerLayout.performClick();
 
@@ -613,6 +623,7 @@
 
     @Test
     public void onItemClick_clickDevice_verifyConnectDevice() {
+        when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
         assertThat(mMediaDevice2.getState()).isEqualTo(
                 LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -623,6 +634,21 @@
     }
 
     @Test
+    public void onItemClick_clickDeviceWithSessionOngoing_verifyShowsDialog() {
+        when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true);
+        assertThat(mMediaDevice2.getState()).isEqualTo(
+                LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+        MediaOutputAdapter.MediaDeviceViewHolder spyMediaDeviceViewHolder = spy(mViewHolder);
+
+        mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 0);
+        mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1);
+        spyMediaDeviceViewHolder.mContainerLayout.performClick();
+
+        verify(mMediaOutputController, never()).connectDevice(mMediaDevice2);
+        verify(spyMediaDeviceViewHolder).showCustomEndSessionDialog(mMediaDevice2);
+    }
+
+    @Test
     public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() {
         when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
         when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
@@ -700,6 +726,7 @@
                 mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
                         Collectors.toList()));
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mMediaOutputAdapter.updateItems();
         mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
                 .onCreateViewHolder(new LinearLayout(mContext), 0);
         List<MediaDevice> selectableDevices = new ArrayList<>();
@@ -734,4 +761,18 @@
 
         verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true);
     }
+
+    @Test
+    public void updateItems_controllerItemsUpdated_notUpdatesInAdapterUntilUpdateItems() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        mMediaOutputAdapter.updateItems();
+        List<MediaItem> updatedList = new ArrayList<>();
+        updatedList.add(new MediaItem());
+        when(mMediaOutputController.getMediaItemList()).thenReturn(updatedList);
+        assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size());
+
+        mMediaOutputAdapter.updateItems();
+
+        assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(updatedList.size());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index f206409..480d59c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -239,7 +239,7 @@
         when(mMediaOutputBaseAdapter.isDragging()).thenReturn(false);
         mMediaOutputBaseDialogImpl.refresh();
 
-        verify(mMediaOutputBaseAdapter).notifyDataSetChanged();
+        verify(mMediaOutputBaseAdapter).updateItems();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 21a7a34..425d0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -39,6 +39,7 @@
 import android.util.FeatureFlagUtils;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -64,6 +65,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 
 @MediumTest
 @RunWith(AndroidTestingRunner.class)
@@ -89,7 +91,7 @@
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
-    private final MediaDescription  mMediaDescription = mock(MediaDescription.class);
+    private final MediaDescription mMediaDescription = mock(MediaDescription.class);
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
             NearbyMediaDevicesManager.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
@@ -102,6 +104,11 @@
     private MediaOutputController mMediaOutputController;
     private final List<String> mFeatures = new ArrayList<>();
 
+    @Override
+    protected boolean shouldFailOnLeakedReceiver() {
+        return true;
+    }
+
     @Before
     public void setUp() {
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
@@ -120,8 +127,7 @@
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
-        mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mMediaOutputController, mUiEventLogger);
+        mMediaOutputDialog = makeTestDialog(mMediaOutputController);
         mMediaOutputDialog.show();
 
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -130,7 +136,7 @@
 
     @After
     public void tearDown() {
-        mMediaOutputDialog.dismissDialog();
+        mMediaOutputDialog.dismiss();
     }
 
     @Test
@@ -311,11 +317,9 @@
         MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
         when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
 
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mockMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        withTestDialog(mockMediaOutputController, testDialog -> {
+            assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        });
     }
 
     @Test
@@ -328,11 +332,9 @@
         when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true);
         when(mockMediaOutputController.isPlaying()).thenReturn(true);
         when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mockMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        withTestDialog(mockMediaOutputController, testDialog -> {
+            assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        });
     }
 
     @Test
@@ -341,11 +343,9 @@
         when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
         when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null);
         when(mockMediaOutputController.isPlaying()).thenReturn(false);
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mockMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        testDialog.onStopButtonClick();
+        withTestDialog(mockMediaOutputController, testDialog -> {
+            testDialog.onStopButtonClick();
+        });
 
         verify(mockMediaOutputController).releaseSession();
     }
@@ -354,13 +354,22 @@
     // Check the visibility metric logging by creating a new MediaOutput dialog,
     // and verify if the calling times increases.
     public void onCreate_ShouldLogVisibility() {
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        testDialog.dismissDialog();
+        withTestDialog(mMediaOutputController, testDialog -> {});
 
         verify(mUiEventLogger, times(2))
                 .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW);
     }
+
+    @NonNull
+    private MediaOutputDialog makeTestDialog(MediaOutputController controller) {
+        return new MediaOutputDialog(mContext, false, mBroadcastSender,
+                controller, mUiEventLogger);
+    }
+
+    private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) {
+        MediaOutputDialog testDialog = makeTestDialog(controller);
+        testDialog.show();
+        c.accept(testDialog);
+        testDialog.dismiss();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 0e2a3ac..190ee81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -24,13 +24,13 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -76,7 +76,7 @@
                 .addChange(TRANSIT_CLOSE, 0 /* flags */,
                         createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
                 .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
-                .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */)
+                .addChange(TRANSIT_CHANGE, FLAG_IS_DIVIDER_BAR, null /* taskInfo */)
                 .build();
         // Check apps extraction
         RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
@@ -107,7 +107,7 @@
         RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
                 false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
         assertEquals(1, nonApps.length);
-        assertTrue(nonApps[0].prefixOrderIndex < closeLayer);
+        assertTrue(nonApps[0].prefixOrderIndex == Integer.MAX_VALUE);
         assertEquals(MODE_CHANGING, nonApps[0].mode);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index c7ea09c..2e5afa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -340,6 +340,11 @@
     }
 
     public void setConnectivityViaCallbackInNetworkController(
+            Network network, NetworkCapabilities networkCapabilities) {
+        mDefaultCallbackInNetworkController.onCapabilitiesChanged(network, networkCapabilities);
+    }
+
+    public void setConnectivityViaCallbackInNetworkController(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
         final NetworkCapabilities.Builder builder =
                 new NetworkCapabilities.Builder(mNetCapabilities);
@@ -351,6 +356,13 @@
                 mock(Network.class), builder.build());
     }
 
+    public void setConnectivityViaDefaultAndNormalCallbackInWifiTracker(
+            Network network, NetworkCapabilities networkCapabilities) {
+        mNetworkCallback.onAvailable(network);
+        mNetworkCallback.onCapabilitiesChanged(network, networkCapabilities);
+        mDefaultCallbackInWifiTracker.onCapabilitiesChanged(network, networkCapabilities);
+    }
+
     public void setConnectivityViaCallbackInWifiTracker(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
         final NetworkCapabilities.Builder builder =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 68170ea..44a1c50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assert.assertTrue;
@@ -25,6 +28,7 @@
 
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.vcn.VcnTransportInfo;
@@ -43,6 +47,8 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.Collections;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -269,6 +275,83 @@
         }
     }
 
+    /** Test for b/225902574. */
+    @Test
+    public void vcnOnlyOnUnderlyingNetwork() {
+        setWifiEnabled(true);
+
+        // Set up a carrier merged network...
+        WifiInfo underlyingCarrierMergedInfo = Mockito.mock(WifiInfo.class);
+        when(underlyingCarrierMergedInfo.isCarrierMerged()).thenReturn(true);
+        when(underlyingCarrierMergedInfo.isPrimary()).thenReturn(true);
+        int zeroLevel = 0;
+        when(underlyingCarrierMergedInfo.getRssi()).thenReturn(calculateRssiForLevel(zeroLevel));
+
+        NetworkCapabilities underlyingNetworkCapabilities = Mockito.mock(NetworkCapabilities.class);
+        when(underlyingNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
+                .thenReturn(true);
+        when(underlyingNetworkCapabilities.getTransportInfo())
+                .thenReturn(underlyingCarrierMergedInfo);
+
+        Network underlyingNetwork = Mockito.mock(Network.class);
+        when(mMockCm.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingNetworkCapabilities);
+
+        NetworkCapabilities.Builder mainCapabilitiesBuilder = new NetworkCapabilities.Builder();
+        mainCapabilitiesBuilder.addTransportType(TRANSPORT_CELLULAR);
+        mainCapabilitiesBuilder.setTransportInfo(null);
+        // And make the carrier merged network the underlying network, *not* the main network.
+        mainCapabilitiesBuilder.setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        // WHEN this primary network with underlying carrier merged information is sent
+        setConnectivityViaDefaultAndNormalCallbackInWifiTracker(
+                primaryNetwork, mainCapabilitiesBuilder.build());
+
+        // THEN we see the mobile data indicators for carrier merged
+        verifyLastMobileDataIndicatorsForVcn(
+                /* visible= */ true,
+                /* level= */ zeroLevel,
+                TelephonyIcons.ICON_CWF,
+                /* inet= */ false);
+
+        // For each level...
+        for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
+            int rssi = calculateRssiForLevel(testLevel);
+            when(underlyingCarrierMergedInfo.getRssi()).thenReturn(rssi);
+            // WHEN the new level is sent to the callbacks
+            setConnectivityViaDefaultAndNormalCallbackInWifiTracker(
+                    primaryNetwork, mainCapabilitiesBuilder.build());
+
+            // WHEN the network is validated
+            mainCapabilitiesBuilder.addCapability(NET_CAPABILITY_VALIDATED);
+            setConnectivityViaCallbackInNetworkController(
+                    primaryNetwork, mainCapabilitiesBuilder.build());
+
+            // THEN we see the mobile data indicators with inet=true (no exclamation mark)
+            verifyLastMobileDataIndicatorsForVcn(
+                    /* visible= */ true,
+                    testLevel,
+                    TelephonyIcons.ICON_CWF,
+                    /* inet= */ true);
+
+            // WHEN the network is not validated
+            mainCapabilitiesBuilder.removeCapability(NET_CAPABILITY_VALIDATED);
+            setConnectivityViaCallbackInNetworkController(
+                    primaryNetwork, mainCapabilitiesBuilder.build());
+
+            // THEN we see the mobile data indicators with inet=false (exclamation mark)
+            verifyLastMobileDataIndicatorsForVcn(
+                    /* visible= */ true,
+                    testLevel,
+                    TelephonyIcons.ICON_CWF,
+                    /* inet= */ false);
+        }
+    }
+
     @Test
     public void testDisableWiFiWithVcnWithUnderlyingWifi() {
         String testSsid = "Test VCN SSID";
@@ -290,11 +373,7 @@
     }
 
     protected void setWifiLevel(int level) {
-        float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
-        int rssi = (int) (MIN_RSSI + level * amountPerLevel);
-        // Put RSSI in the middle of the range.
-        rssi += amountPerLevel / 2;
-        when(mWifiInfo.getRssi()).thenReturn(rssi);
+        when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
         setConnectivityViaCallbackInWifiTracker(
                 NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
     }
@@ -312,19 +391,23 @@
     }
 
     protected void setWifiLevelForVcn(int level) {
-        float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
-        int rssi = (int) (MIN_RSSI + level * amountPerLevel);
-        // Put RSSI in the middle of the range.
-        rssi += amountPerLevel / 2;
         when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
         when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
-        when(mWifiInfo.getRssi()).thenReturn(rssi);
+        when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
         when(mWifiInfo.isCarrierMerged()).thenReturn(true);
         when(mWifiInfo.getSubscriptionId()).thenReturn(1);
         setConnectivityViaCallbackInWifiTrackerForVcn(
                 NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
     }
 
+    private int calculateRssiForLevel(int level) {
+        float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
+        int rssi = (int) (MIN_RSSI + level * amountPerLevel);
+        // Put RSSI in the middle of the range.
+        rssi += amountPerLevel / 2;
+        return rssi;
+    }
+
     protected void setWifiStateForVcn(boolean connected, String ssid) {
         when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
         when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
index 746544a..d3f5ade 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
@@ -118,7 +118,10 @@
         assertThat(oldCallbackNotified).isFalse()
     }
 
-    inner class TestGestureDetector : GenericGestureDetector("fakeTag", displayTracker) {
+    inner class TestGestureDetector : GenericGestureDetector(
+            "fakeTag",
+            displayTracker.defaultDisplayId
+    ) {
         var isGestureListening = false
 
         override fun onInputEvent(ev: InputEvent) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index bde05b9..5a887eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
@@ -131,6 +132,7 @@
                 context,
                 IMMEDIATE,
                 scope,
+                FakeAirplaneModeRepository(),
                 wifiRepository,
                 mock(),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 7cc59b6..38c7432e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -34,13 +34,16 @@
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
+import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.telephony.PhoneConstants
 import com.android.settingslib.R
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
@@ -51,25 +54,25 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
-import org.junit.After
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -83,6 +86,9 @@
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
+// to run the callback and this makes the looper place nicely with TestScope etc.
+@TestableLooper.RunWithLooper
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionsRepositoryImpl
 
@@ -90,7 +96,8 @@
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
     private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
     private lateinit var connectivityRepository: ConnectivityRepository
-    private lateinit var wifiRepository: FakeWifiRepository
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var wifiRepository: WifiRepository
     private lateinit var carrierConfigRepository: CarrierConfigRepository
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
@@ -102,7 +109,8 @@
     private val mobileMappings = FakeMobileMappingsProxy()
     private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
 
-    private val scope = CoroutineScope(IMMEDIATE)
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
 
     @Before
     fun setUp() {
@@ -138,11 +146,23 @@
                 context,
                 mock(),
                 mock(),
-                scope,
+                testScope.backgroundScope,
                 mock(),
             )
 
-        wifiRepository = FakeWifiRepository()
+        airplaneModeRepository = FakeAirplaneModeRepository()
+
+        wifiRepository =
+            WifiRepositoryImpl(
+                fakeBroadcastDispatcher,
+                connectivityManager,
+                connectivityRepository,
+                mock(),
+                mock(),
+                FakeExecutor(FakeSystemClock()),
+                testScope.backgroundScope,
+                mock(),
+            )
 
         carrierConfigRepository =
             CarrierConfigRepository(
@@ -150,28 +170,28 @@
                 mock(),
                 mock(),
                 logger,
-                scope,
+                testScope.backgroundScope,
             )
 
         connectionFactory =
             MobileConnectionRepositoryImpl.Factory(
                 fakeBroadcastDispatcher,
                 telephonyManager = telephonyManager,
-                bgDispatcher = IMMEDIATE,
+                bgDispatcher = dispatcher,
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
-                scope = scope,
+                scope = testScope.backgroundScope,
                 carrierConfigRepository = carrierConfigRepository,
             )
         carrierMergedFactory =
             CarrierMergedConnectionRepository.Factory(
                 telephonyManager,
-                scope,
+                testScope.backgroundScope,
                 wifiRepository,
             )
         fullConnectionFactory =
             FullMobileConnectionRepository.Factory(
-                scope = scope,
+                scope = testScope.backgroundScope,
                 logFactory = logBufferFactory,
                 mobileRepoFactory = connectionFactory,
                 carrierMergedRepoFactory = carrierMergedFactory,
@@ -188,46 +208,38 @@
                 mobileMappings,
                 fakeBroadcastDispatcher,
                 context,
-                IMMEDIATE,
-                scope,
+                dispatcher,
+                testScope.backgroundScope,
+                airplaneModeRepository,
                 wifiRepository,
                 fullConnectionFactory,
             )
-    }
 
-    @After
-    fun tearDown() {
-        scope.cancel()
+        testScope.runCurrent()
     }
 
     @Test
     fun testSubscriptions_initiallyEmpty() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>())
         }
 
     @Test
     fun testSubscriptions_listUpdates() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
-
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_removingSub_updatesList() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
-
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
             // WHEN 2 networks show up
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
@@ -241,71 +253,55 @@
 
             // THEN the subscriptions list represents the newest change
             assertThat(latest).isEqualTo(listOf(MODEL_2))
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
-
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(latest).isEqualTo(listOf(MODEL_CM))
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
-
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
-
-            job.cancel()
         }
 
     @Test
     fun testActiveDataSubscriptionId_initialValueIsNull() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null)
         }
 
     @Test
     fun testActiveDataSubscriptionId_updates() =
-        runBlocking(IMMEDIATE) {
-            var active: Int? = null
-
-            val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+        testScope.runTest {
+            val active by collectLastValue(underTest.activeMobileDataSubscriptionId)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             assertThat(active).isEqualTo(SUB_2_ID)
-
-            job.cancel()
         }
 
     @Test
     fun activeSubId_nullIfInvalidSubIdIsReceived() =
-        runBlocking(IMMEDIATE) {
-            var latest: Int? = null
-
-            val job = underTest.activeMobileDataSubscriptionId.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeMobileDataSubscriptionId)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
@@ -316,8 +312,6 @@
                 .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
 
             assertThat(latest).isNull()
-
-            job.cancel()
         }
 
     @Test
@@ -327,23 +321,19 @@
 
     @Test
     fun activeRepo_updatesWithActiveDataId() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionRepository? = null
-            val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeMobileDataRepository)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             assertThat(latest?.subId).isEqualTo(SUB_2_ID)
-
-            job.cancel()
         }
 
     @Test
     fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionRepository? = null
-            val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeMobileDataRepository)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
@@ -354,64 +344,49 @@
                 .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
 
             assertThat(latest).isNull()
-
-            job.cancel()
         }
 
     @Test
     /** Regression test for b/268146648. */
     fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
-        runBlocking(IMMEDIATE) {
-            var activeRepo: MobileConnectionRepository? = null
-            var subscriptions: List<SubscriptionModel>? = null
-
-            val activeRepoJob =
-                underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
-            val subscriptionsJob =
-                underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+        testScope.runTest {
+            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+            val subscriptions by collectLastValue(underTest.subscriptions)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             assertThat(subscriptions).isEmpty()
             assertThat(activeRepo).isNotNull()
-
-            activeRepoJob.cancel()
-            subscriptionsJob.cancel()
         }
 
     @Test
     fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionRepository? = null
-            var subscriptions: List<SubscriptionModel>? = null
-            val activeSubIdJob =
-                underTest.activeMobileDataSubscriptionId
-                    .filterNotNull()
-                    .onEach { latest = underTest.getRepoForSubId(it) }
-                    .launchIn(this)
-            val subscriptionsJob =
-                underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+        testScope.runTest {
+            var latestActiveRepo: MobileConnectionRepository? = null
+            collectLastValue(
+                underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
+                    latestActiveRepo = underTest.getRepoForSubId(it)
+                }
+            )
+
+            val latestSubscriptions by collectLastValue(underTest.subscriptions)
 
             // Active data subscription id is sent, but no subscription change has been posted yet
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             // Subscriptions list is empty
-            assertThat(subscriptions).isEmpty()
+            assertThat(latestSubscriptions).isEmpty()
             // getRepoForSubId does not throw
-            assertThat(latest).isNotNull()
-
-            activeSubIdJob.cancel()
-            subscriptionsJob.cancel()
+            assertThat(latestActiveRepo).isNotNull()
         }
 
     @Test
     fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
-        runBlocking(IMMEDIATE) {
-            var activeRepo: MobileConnectionRepository? = null
-            val job = underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
-            val subscriptionsJob = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+            collectLastValue(underTest.subscriptions)
 
             // GIVEN active repo is updated before the subscription list updates
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
@@ -429,15 +404,12 @@
 
             // THEN the newly request repo has been cached and reused
             assertThat(activeRepo).isSameInstanceAs(newRepo)
-
-            job.cancel()
-            subscriptionsJob.cancel()
         }
 
     @Test
     fun testConnectionRepository_validSubId_isCached() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1))
@@ -447,16 +419,15 @@
             val repo2 = underTest.getRepoForSubId(SUB_1_ID)
 
             assertThat(repo1).isSameInstanceAs(repo2)
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionRepository_carrierMergedSubId_isCached() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -465,16 +436,15 @@
             val repo2 = underTest.getRepoForSubId(SUB_CM_ID)
 
             assertThat(repo1).isSameInstanceAs(repo2)
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -483,16 +453,15 @@
             val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -503,26 +472,28 @@
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
 
             // WHEN the wifi network updates to be not carrier merged
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1))
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            runCurrent()
 
             // THEN the repos update
             val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
             mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
             assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
+            runCurrent()
 
             val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
             var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
@@ -530,21 +501,21 @@
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
 
             // WHEN the wifi network updates to be carrier merged
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            runCurrent()
 
             // THEN the repos update
             val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
             mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionCache_clearsInvalidSubscriptions() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -563,16 +534,15 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -591,15 +561,13 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-
-            job.cancel()
         }
 
     /** Regression test for b/261706421 */
     @Test
     fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -617,26 +585,20 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(underTest.getSubIdRepoCache()).isEmpty()
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionRepository_invalidSubId_throws() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
-
+        testScope.runTest {
             assertThrows(IllegalArgumentException::class.java) {
                 underTest.getRepoForSubId(SUB_1_ID)
             }
-
-            job.cancel()
         }
 
     @Test
     fun connectionRepository_logBufferContainsSubIdInItsName() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -655,15 +617,12 @@
                     eq(tableBufferLogName(SUB_2_ID)),
                     anyInt(),
                 )
-
-            job.cancel()
         }
 
     @Test
     fun testDefaultDataSubId_updatesOnBroadcast() =
-        runBlocking(IMMEDIATE) {
-            var latest: Int? = null
-            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
 
@@ -686,28 +645,24 @@
             }
 
             assertThat(latest).isEqualTo(SUB_1_ID)
-
-            job.cancel()
         }
 
     @Test
     fun defaultDataSubId_fetchesInitialValueOnStart() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             subscriptionManagerProxy.defaultDataSubId = 2
-            var latest: Int? = null
-            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isEqualTo(2)
-
-            job.cancel()
         }
 
     @Test
     fun defaultDataSubId_fetchesCurrentOnRestart() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             subscriptionManagerProxy.defaultDataSubId = 2
             var latest: Int? = null
             var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo(2)
 
@@ -720,6 +675,7 @@
             subscriptionManagerProxy.defaultDataSubId = 1
 
             job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo(1)
 
@@ -733,43 +689,37 @@
 
     @Test
     fun mobileIsDefault_capsHaveCellular_isDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
             val caps =
@@ -778,151 +728,144 @@
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_wifiDefault_mobileNotDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_ethernetDefault_mobileNotDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             val underlyingNetwork = mock<Network>()
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val underlyingWifiCapabilities =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
@@ -941,23 +884,23 @@
                 }
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
 
             // THEN there's a carrier merged connection
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             val underlyingCarrierMergedNetwork = mock<Network>()
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val underlyingCapabilities =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
@@ -977,22 +920,19 @@
                 }
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
 
             // THEN there's a carrier merged connection
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
-    fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+    fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
-            // WHEN the default callback isn't carrier merged
+            // WHEN the default callback is TRANSPORT_WIFI but not carrier merged
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
             val caps =
@@ -1001,16 +941,57 @@
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
 
             // BUT the wifi repo has gotten updates that it *is* carrier merged
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
 
             // THEN hasCarrierMergedConnection is true
             assertThat(latest).isTrue()
+        }
 
-            job.cancel()
+    /** Regression test for b/278618530. */
+    @Test
+    fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
+
+            // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                }
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            // BUT the wifi repo has gotten updates that it *is* carrier merged
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+
+            // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
+            // takes precedence over the wifi network being carrier merged.)
+            assertThat(latest).isFalse()
+        }
+
+    /** Regression test for b/278618530. */
+    @Test
+    fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
+
+            // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                }
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            // BUT the wifi repo has gotten updates that it *is* carrier merged
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            // AND we're in airplane mode
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            // THEN hasCarrierMergedConnection is true.
+            assertThat(latest).isTrue()
         }
 
     @Test
@@ -1020,43 +1001,37 @@
 
     @Test
     fun defaultConnectionIsValidated_capsHaveValidated_isValidated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnectionIsValidated)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(false)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnectionIsValidated)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun config_initiallyFromContext() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             overrideResource(R.bool.config_showMin3G, true)
             val configFromContext = MobileMappings.Config.readConfig(context)
             assertThat(configFromContext.showAtLeast3G).isTrue()
@@ -1074,26 +1049,24 @@
                     mobileMappings,
                     fakeBroadcastDispatcher,
                     context,
-                    IMMEDIATE,
-                    scope,
+                    dispatcher,
+                    testScope.backgroundScope,
+                    airplaneModeRepository,
                     wifiRepository,
                     fullConnectionFactory,
                 )
 
-            var latest: MobileMappings.Config? = null
-            val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultDataSubRatConfig)
 
             assertTrue(latest!!.areEqual(configFromContext))
             assertTrue(latest!!.showAtLeast3G)
-
-            job.cancel()
         }
 
     @Test
     fun config_subIdChangeEvent_updated() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileMappings.Config? = null
-            val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultDataSubRatConfig)
+
             assertThat(latest!!.showAtLeast3G).isFalse()
 
             overrideResource(R.bool.config_showMin3G, true)
@@ -1112,15 +1085,13 @@
             // THEN the config is updated
             assertTrue(latest!!.areEqual(configFromContext))
             assertTrue(latest!!.showAtLeast3G)
-
-            job.cancel()
         }
 
     @Test
     fun config_carrierConfigChangeEvent_updated() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileMappings.Config? = null
-            val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultDataSubRatConfig)
+
             assertThat(latest!!.showAtLeast3G).isFalse()
 
             overrideResource(R.bool.config_showMin3G, true)
@@ -1138,15 +1109,12 @@
             // THEN the config is updated
             assertThat(latest!!.areEqual(configFromContext)).isTrue()
             assertThat(latest!!.showAtLeast3G).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activeDataChange_inSameGroup_emitsUnit() =
-        runBlocking(IMMEDIATE) {
-            var latest: Unit? = null
-            val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeSubChangedInGroupEvent)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
@@ -1154,15 +1122,12 @@
                 .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
 
             assertThat(latest).isEqualTo(Unit)
-
-            job.cancel()
         }
 
     @Test
     fun activeDataChange_notInSameGroup_doesNotEmit() =
-        runBlocking(IMMEDIATE) {
-            var latest: Unit? = null
-            val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeSubChangedInGroupEvent)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
@@ -1170,38 +1135,46 @@
                 .onActiveDataSubscriptionIdChanged(SUB_1_ID)
 
             assertThat(latest).isEqualTo(null)
-
-            job.cancel()
         }
 
-    private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+    private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+        runCurrent()
         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
         verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
-    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+    // Note: This is used to update the [WifiRepository].
+    private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback {
+        runCurrent()
+        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+        verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun TestScope.getSubscriptionCallback():
+        SubscriptionManager.OnSubscriptionsChangedListener {
+        runCurrent()
         val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
         verify(subscriptionManager)
             .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
-    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+    private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
+        runCurrent()
         val callbackCaptor = argumentCaptor<TelephonyCallback>()
         verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
         return callbackCaptor.allValues
     }
 
-    private inline fun <reified T> getTelephonyCallbackForType(): T {
-        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+    private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
+        val cbs = this.getTelephonyCallbacks().filterIsInstance<T>()
         assertThat(cbs.size).isEqualTo(1)
         return cbs[0]
     }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-
         // Subscription 1
         private const val SUB_1_ID = 1
         private val GROUP_1 = ParcelUuid(UUID.randomUUID())
@@ -1259,11 +1232,30 @@
         private val SUB_CM =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) }
         private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID)
-        private val WIFI_NETWORK_CM =
-            WifiNetworkModel.CarrierMerged(
-                networkId = 3,
-                subscriptionId = SUB_CM_ID,
-                level = 1,
-            )
+
+        private val WIFI_INFO_CM =
+            mock<WifiInfo>().apply {
+                whenever(this.isPrimary).thenReturn(true)
+                whenever(this.isCarrierMerged).thenReturn(true)
+                whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
+            }
+        private val WIFI_NETWORK_CAPS_CM =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(WIFI_INFO_CM)
+                whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
+            }
+
+        private val WIFI_INFO_ACTIVE =
+            mock<WifiInfo>().apply {
+                whenever(this.isPrimary).thenReturn(true)
+                whenever(this.isCarrierMerged).thenReturn(false)
+            }
+        private val WIFI_NETWORK_CAPS_ACTIVE =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)
+                whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
+            }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 6e1ab58..1fb76b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -272,6 +272,52 @@
         }
 
     @Test
+    fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() =
+        testScope.runTest {
+            val (sub1, sub3) =
+                createSubscriptionPair(
+                    subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+                    opportunistic = Pair(true, true),
+                    grouped = true,
+                )
+            connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            connectivityRepository.vcnSubId.value = SUB_3_ID
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf(sub3))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() =
+        testScope.runTest {
+            val (sub1, sub3) =
+                createSubscriptionPair(
+                    subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+                    opportunistic = Pair(true, true),
+                    grouped = true,
+                )
+            connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            connectivityRepository.vcnSubId.value = SUB_1_ID
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf(sub1))
+
+            job.cancel()
+        }
+
+    @Test
     fun activeDataConnection_turnedOn() =
         testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
@@ -361,6 +407,21 @@
             job.cancel()
         }
 
+    @Test
+    fun failedConnection_carrierMergedDefault_notValidated_failed() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.hasCarrierMergedConnection.value = true
+            connectionsRepository.defaultConnectionIsValidated.value = false
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     /** Regression test for b/275076959. */
     @Test
     fun failedConnection_dataSwitchInSameGroup_notFailed() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 661002d..fa4e91b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -24,6 +24,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -37,6 +38,7 @@
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -655,6 +657,139 @@
         }
 
     @Test
+    fun vcnSubId_initiallyNull() {
+        assertThat(underTest.vcnSubId.value).isNull()
+    }
+
+    @Test
+    fun vcnSubId_tracksVcnTransportInfo() =
+        testScope.runTest {
+            val vcnInfo = VcnTransportInfo(SUB_1_ID)
+
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(vcnInfo)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_filersOutInvalid() =
+        testScope.runTest {
+            val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
+
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(vcnInfo)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_nullIfNoTransportInfo() =
+        testScope.runTest {
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_nullIfVcnInfoIsNotCellular() =
+        testScope.runTest {
+            // If the underlying network of the VCN is a WiFi network, then there is no subId that
+            // could disagree with telephony's active data subscription id.
+
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val wifiInfo = mock<WifiInfo>()
+            val vcnInfo = VcnTransportInfo(wifiInfo)
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(vcnInfo)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_changingVcnInfoIsTracked() =
+        testScope.runTest {
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val wifiInfo = mock<WifiInfo>()
+            val wifiVcnInfo = VcnTransportInfo(wifiInfo)
+            val sub1VcnInfo = VcnTransportInfo(SUB_1_ID)
+            val sub2VcnInfo = VcnTransportInfo(SUB_2_ID)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(wifiVcnInfo)
+                }
+
+            // WIFI VCN info
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+
+            // Cellular VCN info with subId 1
+            whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true)
+            whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+
+            // Cellular VCN info with subId 2
+            whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isEqualTo(SUB_2_ID)
+
+            // No VCN anymore
+            whenever(capabilities.transportInfo).thenReturn(null)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
     fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() {
         val wifiInfo = mock<WifiInfo>()
         val capabilities =
@@ -964,6 +1099,9 @@
         private const val SLOT_WIFI = "wifi"
         private const val SLOT_MOBILE = "mobile"
 
+        private const val SUB_1_ID = 1
+        private const val SUB_2_ID = 2
+
         const val NETWORK_ID = 45
         val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 9e825b70..8f28cc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -30,6 +30,8 @@
     override val defaultConnections: StateFlow<DefaultConnectionModel> =
         MutableStateFlow(DefaultConnectionModel())
 
+    override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
+
     fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
         _forceHiddenIcons.value = hiddenIcons
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2b13705..7402b4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -44,7 +46,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
+import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -69,6 +75,7 @@
     private DumpManager mMockDumpManager;
     private BluetoothControllerImpl mBluetoothControllerImpl;
     private BluetoothAdapter mMockAdapter;
+    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private List<CachedBluetoothDevice> mDevices;
 
@@ -89,17 +96,26 @@
                 .thenReturn(mock(LocalBluetoothProfileManager.class));
         mMockDumpManager = mock(DumpManager.class);
 
-        mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+        BluetoothRepository bluetoothRepository =
+                new FakeBluetoothRepository(mMockBluetoothManager);
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        mBluetoothControllerImpl = new BluetoothControllerImpl(
+                mContext,
+                mFakeFeatureFlags,
                 mUserTracker,
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
+                bluetoothRepository,
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager,
                 mMockAdapter);
     }
 
     @Test
-    public void testNoConnectionWithDevices() {
+    public void testNoConnectionWithDevices_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
         CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
         when(device.isConnected()).thenReturn(true);
         when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
@@ -113,7 +129,27 @@
     }
 
     @Test
-    public void testOnServiceConnected_updatesConnectionState() {
+    public void testNoConnectionWithDevices_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.isConnected()).thenReturn(true);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        mDevices.add(device);
+        when(mMockLocalAdapter.getConnectionState())
+                .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+
+        mBluetoothControllerImpl.onConnectionStateChanged(null,
+                BluetoothAdapter.STATE_DISCONNECTED);
+
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
+    }
+
+    @Test
+    public void testOnServiceConnected_updatesConnectionState_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
         when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
 
         mBluetoothControllerImpl.onServiceConnected();
@@ -123,6 +159,58 @@
     }
 
     @Test
+    public void testOnServiceConnected_updatesConnectionState_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
+
+        mBluetoothControllerImpl.onServiceConnected();
+
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnecting());
+        assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
+    }
+
+    @Test
+    public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
+        CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
+        when(device1Disconnected.isConnected()).thenReturn(false);
+        mDevices.add(device1Disconnected);
+
+        CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
+        when(device2Connected.isConnected()).thenReturn(true);
+        mDevices.add(device2Connected);
+
+        mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
+        mBluetoothControllerImpl.onDeviceAdded(device2Connected);
+
+        assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
+        assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
+                .isEqualTo(device2Connected);
+    }
+
+    @Test
+    public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
+        when(device1Disconnected.isConnected()).thenReturn(false);
+        mDevices.add(device1Disconnected);
+
+        CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
+        when(device2Connected.isConnected()).thenReturn(true);
+        mDevices.add(device2Connected);
+
+        mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
+        mBluetoothControllerImpl.onDeviceAdded(device2Connected);
+
+        assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
+        assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
+                .isEqualTo(device2Connected);
+    }
+
+    @Test
     public void testOnBluetoothStateChange_updatesBluetoothState() {
         mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
 
@@ -147,8 +235,9 @@
     }
 
     @Test
-    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection()
-            throws Exception {
+    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
@@ -168,6 +257,29 @@
     }
 
     @Test
+    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+        mBluetoothControllerImpl.addCallback(callback);
+
+        assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        mDevices.add(device);
+        when(device.isConnected()).thenReturn(true);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        reset(callback);
+        mBluetoothControllerImpl.onAclConnectionStateChanged(device,
+                BluetoothProfile.STATE_CONNECTED);
+
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
+        verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
+    }
+
+
+    @Test
     public void testOnActiveDeviceChanged_updatesAudioActive() {
         assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive());
         assertFalse(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
new file mode 100644
index 0000000..6f40f15
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.bluetooth
+
+import android.bluetooth.BluetoothProfile
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+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.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class BluetoothRepositoryImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: BluetoothRepositoryImpl
+
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
+    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
+
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
+
+        underTest =
+            BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_currentDevicesEmpty_maxStateIsManagerState() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+
+        val status = fetchConnectionStatus(currentDevices = emptyList())
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_currentDevicesEmpty_nullManager_maxStateIsDisconnected() {
+        // This CONNECTING state should be unused because localBluetoothManager is null
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+        underTest =
+            BluetoothRepositoryImpl(
+                testScope.backgroundScope,
+                dispatcher,
+                localBluetoothManager = null,
+            )
+
+        val status = fetchConnectionStatus(currentDevices = emptyList())
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_managerStateLargerThanDeviceStates_maxStateIsManager() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+        val device1 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+            }
+        val device2 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_oneCurrentDevice_maxStateIsDeviceState() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+        val device =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device))
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_multipleDevices_maxStateIsHighestState() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+
+        val device1 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+                whenever(it.isConnected).thenReturn(false)
+            }
+        val device2 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
+                whenever(it.isConnected).thenReturn(true)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTED)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_devicesNotConnected_maxStateIsDisconnected() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+
+        // WHEN the devices say their state is CONNECTED but [isConnected] is false
+        val device1 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
+                whenever(it.isConnected).thenReturn(false)
+            }
+        val device2 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
+                whenever(it.isConnected).thenReturn(false)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
+
+        // THEN the max state is DISCONNECTED
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_currentDevicesEmpty_connectedDevicesEmpty() {
+        val status = fetchConnectionStatus(currentDevices = emptyList())
+
+        assertThat(status.connectedDevices).isEmpty()
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_oneCurrentDeviceDisconnected_connectedDevicesEmpty() {
+        val device =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device))
+
+        assertThat(status.connectedDevices).isEmpty()
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_oneCurrentDeviceConnected_connectedDevicesHasDevice() {
+        val device =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device))
+
+        assertThat(status.connectedDevices).isEqualTo(listOf(device))
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_multipleDevices_connectedDevicesHasOnlyConnected() {
+        val device1Connected =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
+        val device2Disconnected =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) }
+        val device3Connected =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
+
+        val status =
+            fetchConnectionStatus(
+                currentDevices = listOf(device1Connected, device2Disconnected, device3Connected)
+            )
+
+        assertThat(status.connectedDevices).isEqualTo(listOf(device1Connected, device3Connected))
+    }
+
+    private fun fetchConnectionStatus(
+        currentDevices: Collection<CachedBluetoothDevice>
+    ): ConnectionStatusModel {
+        var receivedStatus: ConnectionStatusModel? = null
+        underTest.fetchConnectionStatusInBackground(currentDevices) { status ->
+            receivedStatus = status
+        }
+        scheduler.runCurrent()
+        return receivedStatus!!
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
new file mode 100644
index 0000000..d8c0f77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.bluetooth
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+
+/**
+ * Fake [BluetoothRepository] that delegates to the real [BluetoothRepositoryImpl].
+ *
+ * We only need this because [BluetoothRepository] is called from Java, which can't use [TestScope],
+ * [StandardTestDispatcher], etc. to create a test version of the repo. This class uses those test
+ * items under-the-hood so Java classes can indirectly access them.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class FakeBluetoothRepository(localBluetoothManager: LocalBluetoothManager) : BluetoothRepository {
+
+    private val scheduler = TestCoroutineScheduler()
+    private val dispatcher = StandardTestDispatcher(scheduler)
+    private val testScope = TestScope(dispatcher)
+
+    private val impl =
+        BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager)
+
+    override fun fetchConnectionStatusInBackground(
+        currentDevices: Collection<CachedBluetoothDevice>,
+        callback: ConnectionStatusFetchedCallback
+    ) {
+        impl.fetchConnectionStatusInBackground(currentDevices, callback)
+        scheduler.runCurrent()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 8fc0a1a..6298506 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -200,6 +200,31 @@
         }
 
     @Test
+    fun onFolded_onScreenTurningOnWithoutDozingThenWithDozing_doesNotLogLatency() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.listenForDozing(this)
+            keyguardRepository.setDozing(false)
+            setAodEnabled(enabled = true)
+
+            yield()
+
+            fold()
+            simulateScreenTurningOn()
+            reset(latencyTracker)
+
+            // Now enable dozing and trigger a second run through the aod animation code. It should
+            // not rerun the animation
+            keyguardRepository.setDozing(true)
+            yield()
+            simulateScreenTurningOn()
+
+            verify(latencyTracker, never()).onActionStart(any())
+            verify(latencyTracker, never()).onActionEnd(any())
+
+            job.cancel()
+        }
+
+    @Test
     fun onFolded_animationCancelled_doesNotLogLatency() =
         runBlocking(IMMEDIATE) {
             val job = underTest.listenForDozing(this)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 1ec4e8c..8bbd58d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -102,7 +102,8 @@
                 mock(Executor.class),
                 mock(DumpManager.class),
                 mock(BroadcastDispatcherLogger.class),
-                mock(UserTracker.class));
+                mock(UserTracker.class),
+                shouldFailOnLeakedReceiver());
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
@@ -141,6 +142,10 @@
         mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator());
     }
 
+    protected boolean shouldFailOnLeakedReceiver() {
+        return false;
+    }
+
     @After
     public void SysuiTeardown() {
         if (mRealInstrumentation != null) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index ad086ff..af940e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserTracker
+import java.lang.IllegalStateException
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executor
 
@@ -37,7 +38,8 @@
     broadcastRunningExecutor: Executor,
     dumpManager: DumpManager,
     logger: BroadcastDispatcherLogger,
-    userTracker: UserTracker
+    userTracker: UserTracker,
+    private val shouldFailOnLeakedReceiver: Boolean
 ) :
     BroadcastDispatcher(
         context,
@@ -85,6 +87,9 @@
     fun cleanUpReceivers(testName: String) {
         registeredReceivers.forEach {
             Log.i(testName, "Receiver not unregistered from dispatcher: $it")
+            if (shouldFailOnLeakedReceiver) {
+                throw IllegalStateException("Receiver not unregistered from dispatcher: $it")
+            }
         }
         registeredReceivers.clear()
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 7463061..a324b2f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -2551,6 +2551,9 @@
     @Override
     public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc)
             throws RemoteException {
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.setTrustedOverlay(sc, true).apply();
+        t.close();
         synchronized (mLock) {
             RemoteAccessibilityConnection connection =
                     mA11yWindowManager.getConnectionLocked(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e894f1c..2d1290c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5329,9 +5329,8 @@
             mA11yOverlayLayers.remove(displayId);
             return;
         }
-        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-        transaction.reparent(sc, parent);
-        transaction.apply();
-        transaction.close();
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.reparent(sc, parent).setTrustedOverlay(sc, true).apply();
+        t.close();
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d290c361..fb94af6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1581,6 +1581,13 @@
         // TODO(b/266379948): Ideally wait for PCC request to finish for a while more
         // (say 100ms) before proceeding further on.
 
+        processResponseLockedForPcc(response, response.getClientState(), requestFlags);
+    }
+
+
+    @GuardedBy("mLock")
+    private void processResponseLockedForPcc(@NonNull FillResponse response,
+            @Nullable Bundle newClientState, int flags) {
         if (DBG) {
             Slog.d(TAG, "DBG: Initial response: " + response);
         }
@@ -1588,12 +1595,15 @@
             response = getEffectiveFillResponse(response);
             if (isEmptyResponse(response)) {
                 // Treat it as a null response.
-                processNullResponseLocked(requestId, requestFlags);
+                processNullResponseLocked(
+                        response != null ? response.getRequestId() : 0,
+                        flags);
+                return;
             }
             if (DBG) {
                 Slog.d(TAG, "DBG: Processed response: " + response);
             }
-            processResponseLocked(response, null, requestFlags);
+            processResponseLocked(response, newClientState, flags);
         }
     }
 
@@ -2490,7 +2500,10 @@
                     if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
                     mClientState = newClientState;
                 }
-                final Dataset dataset = (Dataset) result;
+                Dataset dataset = (Dataset) result;
+                FillResponse temp = new FillResponse.Builder().addDataset(dataset).build();
+                temp = getEffectiveFillResponse(temp);
+                dataset = temp.getDatasets().get(0);
                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
                 if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
@@ -4665,10 +4678,8 @@
         setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
         // Move over the id
         newResponse.setRequestId(oldResponse.getRequestId());
-        // Replace the old response
-        mResponses.put(newResponse.getRequestId(), newResponse);
         // Now process the new response
-        processResponseLocked(newResponse, newClientState, 0);
+        processResponseLockedForPcc(newResponse, newClientState, 0);
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
index f184574..f8830ea 100644
--- a/services/core/java/com/android/server/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -47,7 +47,14 @@
     int STATUS_OK = SoundTrigger.STATUS_OK;
 
     // Attach to a specific underlying STModule
-    Session attach(@NonNull IBinder client, ModuleProperties underlyingModule);
+    /**
+     * Attach to a specific underlying STModule.
+     * @param client - Binder token representing the app client for death notifications
+     * @param underlyingModule - Properties of the underlying STModule to attach to
+     * @param isTrusted - {@code true} if callbacks will be appropriately AppOps attributed by
+     * a trusted component prior to delivery to the ultimate client.
+     */
+    Session attach(@NonNull IBinder client, ModuleProperties underlyingModule, boolean isTrusted);
 
     // Enumerate possible STModules to attach to
     List<ModuleProperties> listModuleProperties(Identity originatorIdentity);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a888a0b..5d3bb31 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2497,13 +2497,10 @@
         final File systemDir = SystemServiceManager.ensureSystemDir();
 
         // TODO: Move creation of battery stats service outside of activity manager service.
-        mBatteryStatsService = new BatteryStatsService(systemContext, systemDir,
-                BackgroundThread.get().getHandler());
-        mBatteryStatsService.getActiveStatistics().readLocked();
-        mBatteryStatsService.scheduleWriteToDisk();
+        mBatteryStatsService = BatteryStatsService.create(systemContext, systemDir,
+                BackgroundThread.getHandler(), this);
         mOnBattery = DEBUG_POWER ? true
                 : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
-        mBatteryStatsService.getActiveStatistics().setCallback(this);
         mOomAdjProfiler.batteryPowerChanged(mOnBattery);
 
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -7514,7 +7511,31 @@
                     "registerUidObserver");
         }
         mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                Binder.getCallingUid());
+                Binder.getCallingUid(), /*uids*/null);
+    }
+
+    /**
+     * Registers a UidObserver with a uid filter.
+     *
+     * @param observer The UidObserver implementation to register.
+     * @param which    A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*.
+     * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this
+     *                 threshold in either direction, onUidStateChanged will be called.
+     * @param callingPackage The name of the calling package.
+     * @param uids     A list of uids to watch. If all uids are to be watched, use
+     *                 registerUidObserver instead.
+     * @throws RemoteException
+     * @return Returns A binder token identifying the UidObserver registration.
+     */
+    @Override
+    public IBinder registerUidObserverForUids(IUidObserver observer, int which, int cutpoint,
+            String callingPackage, int[] uids) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
+        return mUidObserverController.register(observer, which, cutpoint, callingPackage,
+                Binder.getCallingUid(), uids);
     }
 
     @Override
@@ -7522,6 +7543,40 @@
         mUidObserverController.unregister(observer);
     }
 
+    /**
+     * Adds a uid to the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to watch.
+     * @throws RemoteException
+     */
+    @Override
+    public void addUidToObserver(IBinder observerToken, String callingPackage, int uid) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
+        mUidObserverController.addUidToObserver(observerToken, uid);
+    }
+
+    /**
+     * Removes a uid from the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to stop watching.
+     * @throws RemoteException
+     */
+    @Override
+    public void removeUidFromObserver(IBinder observerToken, String callingPackage, int uid) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
+        mUidObserverController.removeUidFromObserver(observerToken, uid);
+    }
+
     @Override
     public boolean isUidActive(int uid, String callingPackage) {
         if (!hasUsageStatsPermission(callingPackage)) {
@@ -18616,7 +18671,7 @@
                 int which, int cutpoint, @NonNull String callingPackage) {
             mNetworkPolicyUidObserver = observer;
             mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                    Binder.getCallingUid());
+                    Binder.getCallingUid(), /*uids*/null);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6360e2a..36da888 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -399,6 +399,20 @@
         mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
     }
 
+    /**
+     * Creates an instance of BatteryStatsService and restores data from stored state.
+     */
+    public static BatteryStatsService create(Context context, File systemDir, Handler handler,
+            BatteryStatsImpl.BatteryCallback callback) {
+        BatteryStatsService service = new BatteryStatsService(context, systemDir, handler);
+        service.mStats.setCallback(callback);
+        synchronized (service.mStats) {
+            service.mStats.readLocked();
+        }
+        service.scheduleWriteToDisk();
+        return service;
+    }
+
     public void publish() {
         LocalServices.addService(BatteryStatsInternal.class, new LocalService());
         ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index d7b22a8..c393213 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2300,6 +2300,8 @@
 
             final Process.ProcessStartResult startResult;
             boolean regularZygote = false;
+            app.mProcessGroupCreated = false;
+            app.mSkipProcessGroupCreation = false;
             if (hostingRecord.usesWebviewZygote()) {
                 startResult = startWebView(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
@@ -2328,18 +2330,28 @@
                         isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
                         allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
+                // By now the process group should have been created by zygote.
+                app.mProcessGroupCreated = true;
             }
 
             if (!regularZygote) {
                 // webview and app zygote don't have the permission to create the nodes
-                final int res = Process.createProcessGroup(uid, startResult.pid);
-                if (res < 0) {
-                    if (res == -OsConstants.ESRCH) {
-                        Slog.e(ActivityManagerService.TAG, "Unable to create process group for "
-                            + app.processName + " (" + startResult.pid + ")");
-                    } else {
-                        throw new AssertionError("Unable to create process group for "
-                            + app.processName + " (" + startResult.pid + ")");
+                synchronized (app) {
+                    if (!app.mSkipProcessGroupCreation) {
+                        // If we're not told to skip the process group creation, go create it.
+                        final int res = Process.createProcessGroup(uid, startResult.pid);
+                        if (res < 0) {
+                            if (res == -OsConstants.ESRCH) {
+                                Slog.e(ActivityManagerService.TAG,
+                                        "Unable to create process group for "
+                                        + app.processName + " (" + startResult.pid + ")");
+                            } else {
+                                throw new AssertionError("Unable to create process group for "
+                                    + app.processName + " (" + startResult.pid + ")");
+                            }
+                        } else {
+                            app.mProcessGroupCreated = true;
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0417b8c..4ec813e 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -424,6 +424,16 @@
      */
     Runnable mSuccessorStartRunnable;
 
+    /**
+     * Whether or not the process group of this process has been created.
+     */
+    volatile boolean mProcessGroupCreated;
+
+    /**
+     * Whether or not we should skip the process group creation.
+     */
+    volatile boolean mSkipProcessGroupCreation;
+
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
             long startUptime, long startElapsedTime) {
         this.mStartUid = startUid;
@@ -1192,8 +1202,26 @@
                 EventLog.writeEvent(EventLogTags.AM_KILL,
                         userId, mPid, processName, mState.getSetAdj(), reason);
                 Process.killProcessQuiet(mPid);
-                if (!asyncKPG) Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
-                ProcessList.killProcessGroup(uid, mPid);
+                final boolean killProcessGroup;
+                if (mHostingRecord != null
+                        && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) {
+                    synchronized (ProcessRecord.this) {
+                        killProcessGroup = mProcessGroupCreated;
+                        if (!killProcessGroup) {
+                            // The process group hasn't been created, request to skip it.
+                            mSkipProcessGroupCreation = true;
+                        }
+                    }
+                } else {
+                    killProcessGroup = true;
+                }
+                if (killProcessGroup) {
+                    if (asyncKPG) {
+                        ProcessList.killProcessGroup(uid, mPid);
+                    } else {
+                        Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
+                    }
+                }
             } else {
                 mPendingStart = false;
             }
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index 790cc7b..5e41dcd 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -27,7 +27,9 @@
 import android.app.ActivityManagerProto;
 import android.app.IUidObserver;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -43,6 +45,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.UUID;
 
 public class UidObserverController {
     /** If a UID observer takes more than this long, send a WTF. */
@@ -79,14 +83,19 @@
         mValidateUids = new ActiveUids(null /* service */, false /* postChangesToAtm */);
     }
 
-    void register(@NonNull IUidObserver observer, int which, int cutpoint,
-            @NonNull String callingPackage, int callingUid) {
+    IBinder register(@NonNull IUidObserver observer, int which, int cutpoint,
+            @NonNull String callingPackage, int callingUid, @Nullable int[] uids) {
+        IBinder token = new Binder("UidObserver-" + callingPackage + "-"
+                + UUID.randomUUID().toString());
+
         synchronized (mLock) {
             mUidObservers.register(observer, new UidObserverRegistration(callingUid,
                     callingPackage, which, cutpoint,
                     ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
-                    == PackageManager.PERMISSION_GRANTED));
+                    == PackageManager.PERMISSION_GRANTED, uids, token));
         }
+
+        return token;
     }
 
     void unregister(@NonNull IUidObserver observer) {
@@ -95,6 +104,42 @@
         }
     }
 
+    void addUidToObserver(@NonNull IBinder observerToken, int uid) {
+        synchronized (mLock) {
+            int i = mUidObservers.beginBroadcast();
+            while (i-- > 0) {
+                var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+                if (reg.getToken().equals(observerToken)) {
+                    reg.addUid(uid);
+                    break;
+                }
+
+                if (i == 0) {
+                    Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
+                }
+            }
+            mUidObservers.finishBroadcast();
+        }
+    }
+
+    void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
+        synchronized (mLock) {
+            int i = mUidObservers.beginBroadcast();
+            while (i-- > 0) {
+                var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+                if (reg.getToken().equals(observerToken)) {
+                    reg.removeUid(uid);
+                    break;
+                }
+
+                if (i == 0) {
+                    Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
+                }
+            }
+            mUidObservers.finishBroadcast();
+        }
+    }
+
     int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
             int procAdj, long procStateSeq, int capability, boolean ephemeral) {
         synchronized (mLock) {
@@ -257,6 +302,10 @@
                 final ChangeRecord item = mActiveUidChanges[j];
                 final long start = SystemClock.uptimeMillis();
                 final int change = item.change;
+                // Is the observer watching this uid?
+                if (!reg.isWatchingUid(item.uid)) {
+                    continue;
+                }
                 // Does the user have permission? Don't send a non user UID change otherwise
                 if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
                         && !reg.mCanInteractAcrossUsers) {
@@ -450,6 +499,8 @@
         private final int mWhich;
         private final int mCutpoint;
         private final boolean mCanInteractAcrossUsers;
+        private final IBinder mToken;
+        private int[] mUids;
 
         /**
          * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -481,16 +532,94 @@
         };
 
         UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
-                boolean canInteractAcrossUsers) {
+                boolean canInteractAcrossUsers, @Nullable int[] uids, @NonNull IBinder token) {
             this.mUid = uid;
             this.mPkg = pkg;
             this.mWhich = which;
             this.mCutpoint = cutpoint;
             this.mCanInteractAcrossUsers = canInteractAcrossUsers;
+
+            if (uids != null) {
+                this.mUids = uids.clone();
+                Arrays.sort(this.mUids);
+            } else {
+                this.mUids = null;
+            }
+
+            this.mToken = token;
+
             mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
                     ? new SparseIntArray() : null;
         }
 
+        boolean isWatchingUid(int uid) {
+            if (mUids == null) {
+                return true;
+            }
+
+            return Arrays.binarySearch(mUids, uid) != -1;
+        }
+
+        void addUid(int uid) {
+            if (mUids == null) {
+                return;
+            }
+
+            int[] temp = mUids;
+            mUids = new int[temp.length + 1];
+            boolean inserted = false;
+            for (int i = 0; i < temp.length; i++) {
+                if (!inserted) {
+                    if (temp[i] < uid) {
+                        mUids[i] = temp[i];
+                    } else if (temp[i] == uid) {
+                        // Duplicate uid, no-op and fallback to the previous array
+                        mUids = temp;
+                        return;
+                    } else {
+                        mUids[i] = uid;
+                        mUids[i + 1] = temp[i];
+                        inserted = true;
+                    }
+                } else {
+                    mUids[i + 1] = temp[i];
+                }
+            }
+
+            if (!inserted) {
+                mUids[temp.length] = uid;
+            }
+        }
+
+        void removeUid(int uid) {
+            if (mUids == null || mUids.length == 0) {
+                return;
+            }
+
+            int[] temp = mUids;
+            mUids = new int[temp.length - 1];
+            boolean removed = false;
+            for (int i = 0; i < temp.length; i++) {
+                if (!removed) {
+                    if (temp[i] == uid) {
+                        removed = true;
+                    } else if (i == temp.length - 1) {
+                        // Uid not found, no-op and fallback to the previous array
+                        mUids = temp;
+                        return;
+                    } else {
+                        mUids[i] = temp[i];
+                    }
+                } else {
+                    mUids[i - 1] = temp[i];
+                }
+            }
+        }
+
+        IBinder getToken() {
+            return mToken;
+        }
+
         void dump(@NonNull PrintWriter pw, @NonNull IUidObserver observer) {
             pw.print("    ");
             UserHandle.formatUid(pw, mUid);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a110169..1f3795a 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -44,6 +44,7 @@
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -1769,6 +1770,11 @@
     @Override
     public void setUidMode(int code, int uid, int mode) {
         setUidMode(code, uid, mode, null);
+        if (code == OP_RUN_ANY_IN_BACKGROUND) {
+            // TODO (b/280869337): Remove this once we have the required data.
+            Slog.wtfStack(TAG, "setUidMode called for RUN_ANY_IN_BACKGROUND by uid: "
+                    + UserHandle.formatUid(Binder.getCallingUid()));
+        }
     }
 
     private void setUidMode(int code, int uid, int mode,
@@ -1944,6 +1950,17 @@
     @Override
     public void setMode(int code, int uid, @NonNull String packageName, int mode) {
         setMode(code, uid, packageName, mode, null);
+        final int callingUid = Binder.getCallingUid();
+        if (code == OP_RUN_ANY_IN_BACKGROUND && mode != MODE_ALLOWED) {
+            // TODO (b/280869337): Remove this once we have the required data.
+            final String callingPackage = ArrayUtils.firstOrNull(getPackagesForUid(callingUid));
+            Slog.wtfStack(TAG,
+                    "RUN_ANY_IN_BACKGROUND for package " + packageName + " changed to mode: "
+                            + modeToName(mode) + " via setMode. Calling package: " + callingPackage
+                            + ", calling uid: " + UserHandle.formatUid(callingUid)
+                            + ", calling pid: " + Binder.getCallingPid()
+                            + ", system pid: " + Process.myPid());
+        }
     }
 
     void setMode(int code, int uid, @NonNull String packageName, int mode,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 773df37..1d8bef1 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1031,7 +1031,8 @@
         synchronized (rolesMap) {
             Pair<Integer, Integer> key = new Pair<>(useCase, role);
             if (!rolesMap.containsKey(key)) {
-                return AudioSystem.SUCCESS;
+                // trying to remove a role for a device that wasn't set
+                return AudioSystem.BAD_VALUE;
             }
             List<AudioDeviceAttributes> roleDevices = rolesMap.get(key);
             List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index aece17e7..4aa256d 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -78,6 +78,23 @@
     /*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
             "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
 
+    /**
+     * Property to force the index based safe volume warnings. Note that usually when the
+     * CSD warnings are active the safe volume warnings are deactivated. In combination with
+     * {@link SoundDoseHelper#SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE} both approaches can be active
+     * at the same time.
+     */
+    private static final String SYSTEM_PROPERTY_SAFEMEDIA_FORCE = "audio.safemedia.force";
+    /** Property for bypassing the index based safe volume approach. */
+    private static final String SYSTEM_PROPERTY_SAFEMEDIA_BYPASS = "audio.safemedia.bypass";
+    /**
+     * Property to force the CSD warnings. Note that usually when the CSD warnings are active the
+     * safe volume warnings are deactivated. In combination with
+     * {@link SoundDoseHelper#SYSTEM_PROPERTY_SAFEMEDIA_FORCE} both approaches can be active
+     * at the same time.
+     */
+    private static final String SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE = "audio.safemedia.csd.force";
+
     // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
     // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
     // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
@@ -830,56 +847,64 @@
     }
 
     private void onConfigureSafeMedia(boolean force, String caller) {
+        updateCsdEnabled(caller);
+
         synchronized (mSafeMediaVolumeStateLock) {
             int mcc = mContext.getResources().getConfiguration().mcc;
             if ((mMcc != mcc) || ((mMcc == 0) && force)) {
                 mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                         com.android.internal.R.integer.config_safe_media_volume_index) * 10;
-
                 initSafeMediaVolumeIndex();
 
-                boolean safeMediaVolumeEnabled =
-                        SystemProperties.getBoolean("audio.safemedia.force", false)
-                                || mContext.getResources().getBoolean(
-                                com.android.internal.R.bool.config_safe_media_volume_enabled);
-                boolean safeMediaVolumeBypass =
-                        SystemProperties.getBoolean("audio.safemedia.bypass", false);
+                updateSafeMediaVolume_l(caller);
 
-                // The persisted state is either "disabled" or "active": this is the state applied
-                // next time we boot and cannot be "inactive"
-                int persistedState;
-                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
-                    persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
-                    // The state can already be "inactive" here if the user has forced it before
-                    // the 30 seconds timeout for forced configuration. In this case we don't reset
-                    // it to "active".
-                    if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
-                        if (mMusicActiveMs == 0) {
-                            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
-                            enforceSafeMediaVolume(caller);
-                        } else {
-                            // We have existing playback time recorded, already confirmed.
-                            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
-                            mLastMusicActiveTimeMs = 0;
-                        }
-                    }
-                } else {
-                    persistedState = SAFE_MEDIA_VOLUME_DISABLED;
-                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
-                }
                 mMcc = mcc;
-                mAudioHandler.sendMessageAtTime(
-                        mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE,
-                                persistedState, /*arg2=*/0,
-                                /*obj=*/null), /*delay=*/0);
             }
-
-            updateCsdEnabled(caller);
         }
     }
 
+    @GuardedBy("mSafeMediaVolumeStateLock")
+    private void updateSafeMediaVolume_l(String caller) {
+        boolean safeMediaVolumeEnabled =
+                SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE, false)
+                        || (mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_safe_media_volume_enabled)
+                        && !mEnableCsd.get());
+        boolean safeMediaVolumeBypass =
+                SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false);
+
+        // The persisted state is either "disabled" or "active": this is the state applied
+        // next time we boot and cannot be "inactive"
+        int persistedState;
+        if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
+            persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
+            // The state can already be "inactive" here if the user has forced it before
+            // the 30 seconds timeout for forced configuration. In this case we don't reset
+            // it to "active".
+            if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
+                if (mMusicActiveMs == 0) {
+                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+                    enforceSafeMediaVolume(caller);
+                } else {
+                    // We have existing playback time recorded, already confirmed.
+                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+                    mLastMusicActiveTimeMs = 0;
+                }
+            }
+        } else {
+            persistedState = SAFE_MEDIA_VOLUME_DISABLED;
+            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+        }
+
+        mAudioHandler.sendMessageAtTime(
+                mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE,
+                        persistedState, /*arg2=*/0,
+                        /*obj=*/null), /*delay=*/0);
+    }
+
     private void updateCsdEnabled(String caller) {
-        boolean newEnableCsd = SystemProperties.getBoolean("audio.safemedia.force", false);
+        boolean newEnableCsd = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
+                false);
         if (!newEnableCsd) {
             final String featureFlagEnableCsdValue = DeviceConfig.getProperty(
                     DeviceConfig.NAMESPACE_MEDIA,
@@ -895,6 +920,10 @@
         if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) {
             Log.i(TAG, caller + ": enable CSD " + newEnableCsd);
             initCsd();
+
+            synchronized (mSafeMediaVolumeStateLock) {
+                updateSafeMediaVolume_l(caller);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index da822fa..3e31bd1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -892,9 +892,8 @@
      * @return true/false if contact sharing is enabled/disabled
      */
     protected boolean isContactSharingAllowedForCloneProfile() {
-        // TODO(b/253449368): This method should also check for the config controlling
-        // all app-cloning features.
-        return mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+        return mContext.getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks)
+                && mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 8277041..f38c6c1 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -226,7 +226,6 @@
         mPm = mContext.getPackageManager();
         mHandler.postMetricsTimer();
         cleanUpZenRules();
-        evaluateZenMode("onSystemReady", true);
         mIsBootComplete = true;
         showZenUpgradeNotification(mZenMode);
     }
@@ -936,7 +935,10 @@
             if (policyChanged) {
                 dispatchOnPolicyChanged();
             }
-            mHandler.postApplyConfig(config, reason, triggeringComponent, setRingerMode);
+            final String val = Integer.toString(config.hashCode());
+            Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+            evaluateZenMode(reason, setRingerMode);
+            mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
             return true;
         } catch (SecurityException e) {
             Log.wtf(TAG, "Invalid rule in config", e);
@@ -946,14 +948,6 @@
         }
     }
 
-    private void applyConfig(ZenModeConfig config, String reason,
-            ComponentName triggeringComponent, boolean setRingerMode) {
-        final String val = Integer.toString(config.hashCode());
-        Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
-        evaluateZenMode(reason, setRingerMode);
-        mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
-    }
-
     private int getZenModeSetting() {
         return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
     }
@@ -987,21 +981,23 @@
         mZenMode = zen;
         setZenModeSetting(mZenMode);
         updateConsolidatedPolicy(reason);
-        updateRingerModeAffectedStreams();
-        if (setRingerMode && (zen != zenBefore || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                && policyHashBefore != mConsolidatedPolicy.hashCode()))) {
-            applyZenToRingerMode();
-        }
-        applyRestrictions();
+        boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || (
+                zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+                        && policyHashBefore != mConsolidatedPolicy.hashCode()));
+        mHandler.postUpdateRingerAndAudio(shouldApplyToRinger);
         if (zen != zenBefore) {
             mHandler.postDispatchOnZenModeChanged();
         }
     }
 
-    private void updateRingerModeAffectedStreams() {
+    private void updateRingerAndAudio(boolean shouldApplyToRinger) {
         if (mAudioManager != null) {
             mAudioManager.updateRingerModeAffectedStreamsInternal();
         }
+        if (shouldApplyToRinger) {
+            applyZenToRingerMode();
+        }
+        applyRestrictions();
     }
 
     private int computeZenMode() {
@@ -1620,22 +1616,7 @@
     private final class H extends Handler {
         private static final int MSG_DISPATCH = 1;
         private static final int MSG_METRICS = 2;
-        private static final int MSG_APPLY_CONFIG = 4;
-
-        private final class ConfigMessageData {
-            public final ZenModeConfig config;
-            public ComponentName triggeringComponent;
-            public final String reason;
-            public final boolean setRingerMode;
-
-            ConfigMessageData(ZenModeConfig config, String reason,
-                    ComponentName triggeringComponent, boolean setRingerMode) {
-                this.config = config;
-                this.reason = reason;
-                this.setRingerMode = setRingerMode;
-                this.triggeringComponent = triggeringComponent;
-            }
-        }
+        private static final int MSG_RINGER_AUDIO = 5;
 
         private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000;
 
@@ -1653,10 +1634,9 @@
             sendEmptyMessageDelayed(MSG_METRICS, METRICS_PERIOD_MS);
         }
 
-        private void postApplyConfig(ZenModeConfig config, String reason,
-                ComponentName triggeringComponent, boolean setRingerMode) {
-            sendMessage(obtainMessage(MSG_APPLY_CONFIG,
-                    new ConfigMessageData(config, reason, triggeringComponent, setRingerMode)));
+        private void postUpdateRingerAndAudio(boolean shouldApplyToRinger) {
+            removeMessages(MSG_RINGER_AUDIO);
+            sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
         }
 
         @Override
@@ -1668,10 +1648,9 @@
                 case MSG_METRICS:
                     mMetrics.emit();
                     break;
-                case MSG_APPLY_CONFIG:
-                    ConfigMessageData applyConfigData = (ConfigMessageData) msg.obj;
-                    applyConfig(applyConfigData.config, applyConfigData.reason,
-                            applyConfigData.triggeringComponent, applyConfigData.setRingerMode);
+                case MSG_RINGER_AUDIO:
+                    boolean shouldApplyToRinger = (boolean) msg.obj;
+                    updateRingerAndAudio(shouldApplyToRinger);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9b1a80be..0bd6dff 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1330,7 +1330,11 @@
         @Override
         public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component,
                 UserHandle user) {
-            ensureShortcutPermission(callingPackage);
+            if (mContext.checkPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS,
+                    injectBinderCallingPid(), injectBinderCallingUid())
+                            != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Permission START_TASKS_FROM_RECENTS required");
+            }
             if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
                 throw new ActivityNotFoundException("Activity could not be found");
             }
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index 3923890..ccd5b0e 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -23,6 +23,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
 
+import com.android.internal.R;
 import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.resolution.ComponentResolverApi;
@@ -61,7 +62,8 @@
             long flags) {
         final long token = Binder.clearCallingIdentity();
         try {
-            return appCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks()
+            return  context.getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks)
+                    && appCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks()
                     && (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0)
                     && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
         } finally {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 97e7f6f..d3f7002 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1645,13 +1645,14 @@
             throw new SecurityException("link() can only be run by the system");
         }
 
+        final File target = new File(path);
+        final File source = new File(stageDir, target.getName());
+        var sourcePath = source.getAbsolutePath();
         try {
-            final File target = new File(path);
-            final File source = new File(stageDir, target.getName());
             try {
-                Os.link(path, source.getAbsolutePath());
+                Os.link(path, sourcePath);
                 // Grant READ access for APK to be read successfully
-                Os.chmod(source.getAbsolutePath(), 0644);
+                Os.chmod(sourcePath, 0644);
             } catch (ErrnoException e) {
                 e.rethrowAsIOException();
             }
@@ -1659,6 +1660,12 @@
                 throw new IOException("Can't relabel file: " + source);
             }
         } catch (IOException e) {
+            try {
+                Os.unlink(sourcePath);
+            } catch (Exception ignored) {
+                Slog.d(TAG, "Failed to unlink session file: " + sourcePath);
+            }
+
             throw ExceptionUtils.wrap(e);
         }
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dc56def..d7eff52 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3096,18 +3096,21 @@
                     }
                     int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
 
+                    float minLinearBrightness = mPowerManager.getBrightnessConstraint(
+                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+                    float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
+                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
                     float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
 
                     float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
                     float adjustedGammaBrightness =
                             gammaBrightness + 1f / BRIGHTNESS_STEPS * direction;
-
+                    adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f,
+                            1f);
                     float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear(
                             adjustedGammaBrightness);
-
-                    adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, 0f,
-                            1f);
-
+                    adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness,
+                            minLinearBrightness, maxLinearBrightness);
                     mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
 
                     startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 1c89ec4..3c5ad2a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1573,7 +1573,7 @@
         @Override
         public void reportUserMayRequestUnlock(int userId) throws RemoteException {
             enforceReportPermission();
-            mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId).sendToTarget();
+            mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId, /*arg2=*/ 0).sendToTarget();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 88c9042..1f5c1cf 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2629,6 +2629,10 @@
                         getSessionLocked(sessionState).notifyAdBufferReady(buffer);
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in notifyAdBuffer", e);
+                    } finally {
+                        if (buffer != null) {
+                            buffer.getSharedMemory().close();
+                        }
                     }
                 }
             } finally {
@@ -3891,10 +3895,13 @@
                     return;
                 }
                 try {
-                    mSessionState.client.onAdBufferConsumed(
-                            buffer, mSessionState.seq);
+                    mSessionState.client.onAdBufferConsumed(buffer, mSessionState.seq);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "error in onAdBufferConsumed", e);
+                } finally {
+                    if (buffer != null) {
+                        buffer.getSharedMemory().close();
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 80cb085..e6273d3 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2007,6 +2007,10 @@
                         getSessionLocked(sessionState).notifyAdBufferConsumed(buffer);
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slogf.e(TAG, "error in notifyAdBufferConsumed", e);
+                    } finally {
+                        if (buffer != null) {
+                            buffer.getSharedMemory().close();
+                        }
                     }
                 }
             } finally {
@@ -3063,6 +3067,10 @@
                     mSessionState.mClient.onAdBufferReady(buffer, mSessionState.mSeq);
                 } catch (RemoteException e) {
                     Slogf.e(TAG, "error in onAdBuffer", e);
+                } finally {
+                    if (buffer != null) {
+                        buffer.getSharedMemory().close();
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 3699557..25c0750 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -33,4 +33,7 @@
 
     /** Notifies when the screen starts turning on and is not yet visible to the user. */
     public abstract void onScreenTurningOn(int displayId);
+
+    /** Notifies when the keyguard is going away. Sent right after the bouncer is gone. */
+    public abstract void onKeyguardGoingAway();
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4c8f513..84d3a21 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1617,6 +1617,11 @@
         public void onScreenTurningOn(int displayId) {
             notifyScreenTurningOn(displayId);
         }
+
+        @Override
+        public void onKeyguardGoingAway() {
+            notifyKeyguardGoingAway();
+        }
     }
 
     void initialize() {
@@ -2654,6 +2659,45 @@
         }
     }
 
+    /**
+     * Propagate a keyguard going away event to the wallpaper engine.
+     */
+    private void notifyKeyguardGoingAway() {
+        synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    data.connection.forEachDisplayConnector(displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
+                                        -1, -1, -1, new Bundle());
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
+                            }
+                        }
+                    });
+                }
+                return;
+            }
+
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null && data.connection != null) {
+                data.connection.forEachDisplayConnector(displayConnector -> {
+                    if (displayConnector.mEngine != null) {
+                        try {
+                            displayConnector.mEngine.dispatchWallpaperCommand(
+                                    WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
+                                    -1, -1, -1, new Bundle());
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }
+        }
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
@@ -2745,6 +2789,20 @@
     }
 
     /**
+     * Returns true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK,
+     * always return false if the lockscreen doesn't run its own wallpaper engine.
+     */
+    @Override
+    public boolean isStaticWallpaper(int which) {
+        synchronized (mLock) {
+            WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap)
+                    .get(mCurrentUserId);
+            if (wallpaperData == null) return false;
+            return mImageWallpaper.equals(wallpaperData.wallpaperComponent);
+        }
+    }
+
+    /**
      * Sets wallpaper dim amount for the calling UID. This applies to all destinations (home, lock)
      * with an active wallpaper engine.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c40d72c..6b90181 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1159,9 +1159,10 @@
             }
             return;
         }
+        transition.collect(topFocusedRootTask);
+        executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
         r.mTransitionController.requestStartTransition(transition, topFocusedRootTask,
                 null /* remoteTransition */, null /* displayChange */);
-        executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
         transition.setReady(topFocusedRootTask, true);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1717d24..cb7c7ae 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1640,7 +1640,7 @@
             if (isState(RESUMED)) {
                 newParent.setResumedActivity(this, "onParentChanged");
             }
-            mLetterboxUiController.onActivityParentChanged(newParent);
+            mLetterboxUiController.updateInheritedLetterbox();
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index b816dad..f35343c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -276,6 +276,7 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wallpaper.WallpaperManagerInternal;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -365,6 +366,7 @@
     private ComponentName mSysUiServiceComponent;
     private PermissionPolicyInternal mPermissionPolicyInternal;
     private StatusBarManagerInternal mStatusBarManagerInternal;
+    private WallpaperManagerInternal mWallpaperManagerInternal;
     @VisibleForTesting
     final ActivityTaskManagerInternal mInternal;
     private PowerManagerInternal mPowerManagerInternal;
@@ -3553,6 +3555,10 @@
                 mRootWindowContainer.forAllDisplays(displayContent -> {
                     mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
                 });
+                WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+                if (wallpaperManagerInternal != null) {
+                    wallpaperManagerInternal.onKeyguardGoingAway();
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -5232,6 +5238,13 @@
         return mStatusBarManagerInternal;
     }
 
+    WallpaperManagerInternal getWallpaperManagerInternal() {
+        if (mWallpaperManagerInternal == null) {
+            mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
+        }
+        return mWallpaperManagerInternal;
+    }
+
     AppWarnings getAppWarningsLocked() {
         return mAppWarnings;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index be52e5a..911591c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -22,6 +22,7 @@
 import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS;
 import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
 import static android.view.InsetsFrameProvider.SOURCE_FRAME;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -184,7 +185,6 @@
 
     private final boolean mCarDockEnablesAccelerometer;
     private final boolean mDeskDockEnablesAccelerometer;
-    private final boolean mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer;
     private final AccessibilityManager mAccessibilityManager;
     private final ImmersiveModeConfirmation mImmersiveModeConfirmation;
     private final ScreenshotHelper mScreenshotHelper;
@@ -214,7 +214,8 @@
         }
     }
 
-    private final SystemGesturesPointerEventListener mSystemGestures;
+    // Will be null in client transient mode.
+    private SystemGesturesPointerEventListener mSystemGestures;
 
     final DecorInsets mDecorInsets;
 
@@ -394,8 +395,6 @@
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
         mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
-        mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer =
-                r.getBoolean(R.bool.config_deskRespectsNoSensorAndLockedWithoutAccelerometer);
         mCanSystemBarsBeShownByUser = !r.getBoolean(
                 R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean(
                 R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction);
@@ -411,158 +410,162 @@
         final Looper looper = UiThread.getHandler().getLooper();
         mHandler = new PolicyHandler(looper);
         // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context.
-        mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
-                new SystemGesturesPointerEventListener.Callbacks() {
+        if (!CLIENT_TRANSIENT) {
+            SystemGesturesPointerEventListener.Callbacks gesturesPointerEventCallbacks =
+                    new SystemGesturesPointerEventListener.Callbacks() {
 
-                    private static final long MOUSE_GESTURE_DELAY_MS = 500;
+                private static final long MOUSE_GESTURE_DELAY_MS = 500;
 
-                    private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
-                    private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
-                    private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
-                    private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;
+                private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
+                private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
+                private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
+                private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;
 
-                    private Insets getControllableInsets(WindowState win) {
-                        if (win == null) {
-                            return Insets.NONE;
-                        }
-                        final InsetsSourceProvider provider = win.getControllableInsetProvider();
-                        if (provider == null) {
-                            return  Insets.NONE;
-                        }
-                        return provider.getSource().calculateInsets(win.getBounds(),
-                                true /* ignoreVisibility */);
+                private Insets getControllableInsets(WindowState win) {
+                    if (win == null) {
+                        return Insets.NONE;
                     }
+                    final InsetsSourceProvider provider = win.getControllableInsetProvider();
+                    if (provider == null) {
+                        return Insets.NONE;
+                    }
+                    return provider.getSource().calculateInsets(win.getBounds(),
+                            true /* ignoreVisibility */);
+                }
 
-                    @Override
-                    public void onSwipeFromTop() {
-                        synchronized (mLock) {
-                            requestTransientBars(mTopGestureHost,
-                                    getControllableInsets(mTopGestureHost).top > 0);
+                @Override
+                public void onSwipeFromTop() {
+                    synchronized (mLock) {
+                        requestTransientBars(mTopGestureHost,
+                                getControllableInsets(mTopGestureHost).top > 0);
+                    }
+                }
+
+                @Override
+                public void onSwipeFromBottom() {
+                    synchronized (mLock) {
+                        requestTransientBars(mBottomGestureHost,
+                                getControllableInsets(mBottomGestureHost).bottom > 0);
+                    }
+                }
+
+                private boolean allowsSideSwipe(Region excludedRegion) {
+                    return mNavigationBarAlwaysShowOnSideGesture
+                            && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                }
+
+                @Override
+                public void onSwipeFromRight() {
+                    final Region excludedRegion = Region.obtain();
+                    synchronized (mLock) {
+                        mDisplayContent.calculateSystemGestureExclusion(
+                                excludedRegion, null /* outUnrestricted */);
+                        final boolean hasWindow =
+                                getControllableInsets(mRightGestureHost).right > 0;
+                        if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                            requestTransientBars(mRightGestureHost, hasWindow);
                         }
                     }
+                    excludedRegion.recycle();
+                }
 
-                    @Override
-                    public void onSwipeFromBottom() {
-                        synchronized (mLock) {
-                            requestTransientBars(mBottomGestureHost,
-                                    getControllableInsets(mBottomGestureHost).bottom > 0);
+                @Override
+                public void onSwipeFromLeft() {
+                    final Region excludedRegion = Region.obtain();
+                    synchronized (mLock) {
+                        mDisplayContent.calculateSystemGestureExclusion(
+                                excludedRegion, null /* outUnrestricted */);
+                        final boolean hasWindow =
+                                getControllableInsets(mLeftGestureHost).left > 0;
+                        if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                            requestTransientBars(mLeftGestureHost, hasWindow);
                         }
                     }
+                    excludedRegion.recycle();
+                }
 
-                    private boolean allowsSideSwipe(Region excludedRegion) {
-                        return mNavigationBarAlwaysShowOnSideGesture
-                                && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                @Override
+                public void onFling(int duration) {
+                    if (mService.mPowerManagerInternal != null) {
+                        mService.mPowerManagerInternal.setPowerBoost(
+                                Boost.INTERACTION, duration);
                     }
+                }
 
-                    @Override
-                    public void onSwipeFromRight() {
-                        final Region excludedRegion = Region.obtain();
-                        synchronized (mLock) {
-                            mDisplayContent.calculateSystemGestureExclusion(
-                                    excludedRegion, null /* outUnrestricted */);
-                            final boolean hasWindow =
-                                    getControllableInsets(mRightGestureHost).right > 0;
-                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
-                                requestTransientBars(mRightGestureHost, hasWindow);
-                            }
-                        }
-                        excludedRegion.recycle();
-                    }
+                @Override
+                public void onDebug() {
+                    // no-op
+                }
 
-                    @Override
-                    public void onSwipeFromLeft() {
-                        final Region excludedRegion = Region.obtain();
-                        synchronized (mLock) {
-                            mDisplayContent.calculateSystemGestureExclusion(
-                                    excludedRegion, null /* outUnrestricted */);
-                            final boolean hasWindow =
-                                    getControllableInsets(mLeftGestureHost).left > 0;
-                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
-                                requestTransientBars(mLeftGestureHost, hasWindow);
-                            }
-                        }
-                        excludedRegion.recycle();
-                    }
+                private WindowOrientationListener getOrientationListener() {
+                    final DisplayRotation rotation = mDisplayContent.getDisplayRotation();
+                    return rotation != null ? rotation.getOrientationListener() : null;
+                }
 
-                    @Override
-                    public void onFling(int duration) {
-                        if (mService.mPowerManagerInternal != null) {
-                            mService.mPowerManagerInternal.setPowerBoost(
-                                    Boost.INTERACTION, duration);
-                        }
+                @Override
+                public void onDown() {
+                    final WindowOrientationListener listener = getOrientationListener();
+                    if (listener != null) {
+                        listener.onTouchStart();
                     }
+                }
 
-                    @Override
-                    public void onDebug() {
-                        // no-op
+                @Override
+                public void onUpOrCancel() {
+                    final WindowOrientationListener listener = getOrientationListener();
+                    if (listener != null) {
+                        listener.onTouchEnd();
                     }
+                }
 
-                    private WindowOrientationListener getOrientationListener() {
-                        final DisplayRotation rotation = mDisplayContent.getDisplayRotation();
-                        return rotation != null ? rotation.getOrientationListener() : null;
-                    }
+                @Override
+                public void onMouseHoverAtLeft() {
+                    mHandler.removeCallbacks(mOnSwipeFromLeft);
+                    mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onDown() {
-                        final WindowOrientationListener listener = getOrientationListener();
-                        if (listener != null) {
-                            listener.onTouchStart();
-                        }
-                    }
+                @Override
+                public void onMouseHoverAtTop() {
+                    mHandler.removeCallbacks(mOnSwipeFromTop);
+                    mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onUpOrCancel() {
-                        final WindowOrientationListener listener = getOrientationListener();
-                        if (listener != null) {
-                            listener.onTouchEnd();
-                        }
-                    }
+                @Override
+                public void onMouseHoverAtRight() {
+                    mHandler.removeCallbacks(mOnSwipeFromRight);
+                    mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onMouseHoverAtLeft() {
-                        mHandler.removeCallbacks(mOnSwipeFromLeft);
-                        mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseHoverAtBottom() {
+                    mHandler.removeCallbacks(mOnSwipeFromBottom);
+                    mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onMouseHoverAtTop() {
-                        mHandler.removeCallbacks(mOnSwipeFromTop);
-                        mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseLeaveFromLeft() {
+                    mHandler.removeCallbacks(mOnSwipeFromLeft);
+                }
 
-                    @Override
-                    public void onMouseHoverAtRight() {
-                        mHandler.removeCallbacks(mOnSwipeFromRight);
-                        mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseLeaveFromTop() {
+                    mHandler.removeCallbacks(mOnSwipeFromTop);
+                }
 
-                    @Override
-                    public void onMouseHoverAtBottom() {
-                        mHandler.removeCallbacks(mOnSwipeFromBottom);
-                        mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseLeaveFromRight() {
+                    mHandler.removeCallbacks(mOnSwipeFromRight);
+                }
 
-                    @Override
-                    public void onMouseLeaveFromLeft() {
-                        mHandler.removeCallbacks(mOnSwipeFromLeft);
-                    }
-
-                    @Override
-                    public void onMouseLeaveFromTop() {
-                        mHandler.removeCallbacks(mOnSwipeFromTop);
-                    }
-
-                    @Override
-                    public void onMouseLeaveFromRight() {
-                        mHandler.removeCallbacks(mOnSwipeFromRight);
-                    }
-
-                    @Override
-                    public void onMouseLeaveFromBottom() {
-                        mHandler.removeCallbacks(mOnSwipeFromBottom);
-                    }
-                });
-        displayContent.registerPointerEventListener(mSystemGestures);
+                @Override
+                public void onMouseLeaveFromBottom() {
+                    mHandler.removeCallbacks(mOnSwipeFromBottom);
+                }
+            };
+            mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
+                    gesturesPointerEventCallbacks);
+            displayContent.registerPointerEventListener(mSystemGestures);
+        }
         mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() {
 
             private Runnable mAppTransitionPending = () -> {
@@ -648,7 +651,9 @@
                 mContext, () -> {
             synchronized (mLock) {
                 onConfigurationChanged();
-                mSystemGestures.onConfigurationChanged();
+                if (!CLIENT_TRANSIENT) {
+                    mSystemGestures.onConfigurationChanged();
+                }
                 mDisplayContent.updateSystemGestureExclusion();
             }
         });
@@ -670,7 +675,9 @@
     }
 
     void systemReady() {
-        mSystemGestures.systemReady();
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.systemReady();
+        }
         if (mService.mPointerLocationEnabled) {
             setPointerLocationEnabled(true);
         }
@@ -707,10 +714,6 @@
         return mDeskDockEnablesAccelerometer;
     }
 
-    boolean isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer() {
-        return mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer;
-    }
-
     public void setPersistentVrModeEnabled(boolean persistentVrModeEnabled) {
         mPersistentVrModeEnabled = persistentVrModeEnabled;
     }
@@ -1315,7 +1318,9 @@
     }
 
     void onDisplayInfoChanged(DisplayInfo info) {
-        mSystemGestures.onDisplayInfoChanged(info);
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.onDisplayInfoChanged(info);
+        }
     }
 
     /**
@@ -1688,7 +1693,9 @@
         // Update the latest display size, cutout.
         mDisplayContent.updateDisplayInfo();
         onConfigurationChanged();
-        mSystemGestures.onConfigurationChanged();
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.onConfigurationChanged();
+        }
     }
 
     /**
@@ -1967,6 +1974,9 @@
 
     @VisibleForTesting
     void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) {
+        if (CLIENT_TRANSIENT) {
+            return;
+        }
         if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) {
             // Swipe-up for navigation bar is disabled during setup
             return;
@@ -2514,8 +2524,6 @@
         pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer);
         pw.print(" mDeskDockEnablesAccelerometer=");
         pw.println(mDeskDockEnablesAccelerometer);
-        pw.print(" mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer=");
-        pw.println(mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer);
         pw.print(prefix); pw.print("mDockMode="); pw.print(Intent.dockStateToString(mDockMode));
         pw.print(" mLidState="); pw.println(WindowManagerFuncs.lidStateToString(mLidState));
         pw.print(prefix); pw.print("mAwake="); pw.print(mAwake);
@@ -2617,7 +2625,9 @@
             final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
             pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info);
         }
-        mSystemGestures.dump(pw, prefix);
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.dump(pw, prefix);
+        }
 
         pw.print(prefix); pw.println("Looper state:");
         mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + "  ");
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a3d233d..87de0f6 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1239,10 +1239,6 @@
                 mDisplayPolicy.isCarDockEnablesAccelerometer();
         final boolean deskDockEnablesAccelerometer =
                 mDisplayPolicy.isDeskDockEnablesAccelerometer();
-        final boolean deskDockRespectsNoSensorAndLockedWithoutAccelerometer =
-                mDisplayPolicy.isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer()
-                        && (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED
-                                || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
 
         @Surface.Rotation
         final int preferredRotation;
@@ -1263,10 +1259,10 @@
                 || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
                 || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
                 && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)
-                && !deskDockRespectsNoSensorAndLockedWithoutAccelerometer) {
+                && !(orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED
+                        || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)) {
             // Ignore sensor when in desk dock unless explicitly enabled.
-            // This case can override the behavior of NOSENSOR, and can also
-            // enable 180 degree rotation while docked.
+            // This case can enable 180 degree rotation while docked.
             preferredRotation = deskDockEnablesAccelerometer ? sensorRotation : mDeskDockRotation;
         } else if (hdmiPlugged && mDemoHdmiRotationLock) {
             // Ignore sensor when plugged into HDMI when demo HDMI rotation lock enabled.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 6ef6fa7..0288e4b 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -113,6 +113,8 @@
 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
@@ -127,8 +129,7 @@
 final class LetterboxUiController {
 
     private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
-            activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing()
-                    && activityRecord.nowVisible;
+            activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing();
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
 
@@ -180,6 +181,10 @@
     // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS
     private final boolean mIsOverrideEnableCompatFakeFocusEnabled;
 
+    // The list of observers for the destroy event of candidate opaque activities
+    // when dealing with translucent activities.
+    private final List<LetterboxUiController> mDestroyListeners = new ArrayList<>();
+
     @Nullable
     private final Boolean mBooleanPropertyAllowOrientationOverride;
     @Nullable
@@ -193,6 +198,10 @@
     @Nullable
     private WindowContainerListener mLetterboxConfigListener;
 
+    @Nullable
+    @VisibleForTesting
+    ActivityRecord mFirstOpaqueActivityBeneath;
+
     private boolean mShowWallpaperForLetterboxBackground;
 
     // In case of transparent activities we might need to access the aspectRatio of the
@@ -353,6 +362,10 @@
             mLetterbox.destroy();
             mLetterbox = null;
         }
+        for (int i = mDestroyListeners.size() - 1; i >= 0; i--) {
+            mDestroyListeners.get(i).updateInheritedLetterbox();
+        }
+        mDestroyListeners.clear();
         if (mLetterboxConfigListener != null) {
             mLetterboxConfigListener.onRemoved();
             mLetterboxConfigListener = null;
@@ -819,13 +832,14 @@
 
             // Get the bounds of the "space-to-fill". The transformed bounds have the highest
             // priority because the activity is launched in a rotated environment. In multi-window
-            // mode, the task-level represents this. In fullscreen-mode, the task container does
+            // mode, the taskFragment-level represents this for both split-screen
+            // and activity-embedding. In fullscreen-mode, the task container does
             // (since the orientation letterbox is also applied to the task).
             final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
             final Rect spaceToFill = transformedBounds != null
                     ? transformedBounds
                     : mActivityRecord.inMultiWindowMode()
-                            ? mActivityRecord.getTask().getBounds()
+                            ? mActivityRecord.getTaskFragment().getBounds()
                             : mActivityRecord.getRootTask().getParent().getBounds();
             // In case of translucent activities an option is to use the WindowState#getFrame() of
             // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
@@ -1571,7 +1585,11 @@
      * first opaque activity beneath.
      * @param parent The parent container.
      */
-    void onActivityParentChanged(WindowContainer<?> parent) {
+    void updateInheritedLetterbox() {
+        final WindowContainer<?> parent = mActivityRecord.getParent();
+        if (parent == null) {
+            return;
+        }
         if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
             return;
         }
@@ -1581,22 +1599,24 @@
         }
         // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
         // opaque activity constraints because we're expecting the activity is already letterboxed.
+        mFirstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
+                FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
+                mActivityRecord /* boundary */, false /* includeBoundary */,
+                true /* traverseTopToBottom */);
+        if (mFirstOpaqueActivityBeneath == null || mFirstOpaqueActivityBeneath.isEmbedded()) {
+            // We skip letterboxing if the translucent activity doesn't have any opaque
+            // activities beneath or the activity below is embedded which never has letterbox.
+            mActivityRecord.recomputeConfiguration();
+            return;
+        }
         if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
                 || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) {
             return;
         }
-        final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
-                FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
-                mActivityRecord /* boundary */, false /* includeBoundary */,
-                true /* traverseTopToBottom */);
-        if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) {
-            // We skip letterboxing if the translucent activity doesn't have any opaque
-            // activities beneath or the activity below is embedded which never has letterbox.
-            return;
-        }
-        inheritConfiguration(firstOpaqueActivityBeneath);
+        mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.add(this);
+        inheritConfiguration(mFirstOpaqueActivityBeneath);
         mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
-                mActivityRecord, firstOpaqueActivityBeneath,
+                mActivityRecord, mFirstOpaqueActivityBeneath,
                 (opaqueConfig, transparentOverrideConfig) -> {
                     resetTranslucentOverrideConfig(transparentOverrideConfig);
                     final Rect parentBounds = parent.getWindowConfiguration().getBounds();
@@ -1610,7 +1630,7 @@
                     // We need to initialize appBounds to avoid NPE. The actual value will
                     // be set ahead when resolving the Configuration for the activity.
                     transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect());
-                    inheritConfiguration(firstOpaqueActivityBeneath);
+                    inheritConfiguration(mFirstOpaqueActivityBeneath);
                     return transparentOverrideConfig;
                 });
     }
@@ -1684,10 +1704,7 @@
         if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) {
             return Optional.empty();
         }
-        return Optional.ofNullable(mActivityRecord.getTask().getActivity(
-                FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
-                mActivityRecord /* boundary */, false /* includeBoundary */,
-                true /* traverseTopToBottom */));
+        return Optional.ofNullable(mFirstOpaqueActivityBeneath);
     }
 
     /** Resets the screen size related fields so they can be resolved by requested bounds later. */
@@ -1718,6 +1735,10 @@
     }
 
     private void clearInheritedConfig() {
+        if (mFirstOpaqueActivityBeneath != null) {
+            mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.remove(this);
+        }
+        mFirstOpaqueActivityBeneath = null;
         mLetterboxConfigListener = null;
         mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
         mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 50bf38b..bfd0d96 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -314,7 +314,6 @@
 
         mLogger.mCreateWallTimeMs = System.currentTimeMillis();
         mLogger.mCreateTimeNs = SystemClock.elapsedRealtimeNanos();
-        controller.mTransitionTracer.logState(this);
     }
 
     @Nullable
@@ -532,7 +531,6 @@
 
         mLogger.mSyncId = mSyncId;
         mLogger.mCollectTimeNs = SystemClock.elapsedRealtimeNanos();
-        mController.mTransitionTracer.logState(this);
     }
 
     /**
@@ -555,7 +553,6 @@
         applyReady();
 
         mLogger.mStartTimeNs = SystemClock.elapsedRealtimeNanos();
-        mController.mTransitionTracer.logState(this);
 
         mController.updateAnimatingState(mTmpTransaction);
         // merge into the next-time the global transaction is applied. This is too-early to set
@@ -1232,7 +1229,6 @@
         validateVisibility();
 
         mState = STATE_FINISHED;
-        mController.mTransitionTracer.logState(this);
         // Rotation change may be deferred while there is a display change transition, so check
         // again in case there is a new pending change.
         if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
@@ -1261,6 +1257,7 @@
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
         mState = STATE_ABORT;
+        mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos();
         mController.mTransitionTracer.logAbortedTransition(this);
         // Syncengine abort will call through to onTransactionReady()
         mSyncEngine.abort(mSyncId);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c9316bf..b697ab1 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -941,7 +941,6 @@
         }
         mPlayingTransitions.add(transition);
         updateRunningRemoteAnimation(transition, true /* isPlaying */);
-        mTransitionTracer.logState(transition);
         // Sync engine should become idle after this, so the idle listener will check the queue.
     }
 
@@ -1122,7 +1121,6 @@
                 mLatestOnTopTasksReported.clear();
             }
         }
-        mTransitionTracer.logState(transition);
         // This is called during Transition.abort whose codepath will eventually check the queue
         // via sync-engine idle.
     }
@@ -1416,12 +1414,11 @@
         long mReadyTimeNs;
         long mSendTimeNs;
         long mFinishTimeNs;
+        long mAbortTimeNs;
         TransitionRequestInfo mRequest;
         WindowContainerTransaction mStartWCT;
         int mSyncId;
         TransitionInfo mInfo;
-        ProtoOutputStream mProtoOutputStream = new ProtoOutputStream();
-        long mProtoToken;
 
         private String buildOnSendLog() {
             StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index 6aac81b..afc1492 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -50,10 +50,11 @@
     private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
     private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
     static final String WINSCOPE_EXT = ".winscope";
-    private static final String TRACE_FILE = "/data/misc/wmtrace/transition_trace" + WINSCOPE_EXT;
+    private static final String TRACE_FILE =
+            "/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT;
     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
 
-    private final TransitionTraceBuffer mTraceBuffer = new TransitionTraceBuffer();
+    private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
 
     private final Object mEnabledLock = new Object();
     private volatile boolean mActiveTracingEnabled = false;
@@ -72,18 +73,22 @@
      */
     public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets,
             TransitionInfo info) {
-        // Dump the info to proto that will not be available when the transition finishes or
-        // is canceled
-        final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream;
-        transition.mLogger.mProtoToken = outputStream
-                .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS);
+        final ProtoOutputStream outputStream = new ProtoOutputStream();
+        final long protoToken = outputStream
+                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+        outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+        outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
+                transition.mLogger.mCreateTimeNs);
+        outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
+                transition.mLogger.mSendTimeNs);
         outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
                 transition.getStartTransaction().getId());
         outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
                 transition.getFinishTransaction().getId());
         dumpTransitionTargetsToProto(outputStream, transition, targets);
+        outputStream.end(protoToken);
 
-        logTransitionInfo(transition, info);
+        mTraceBuffer.add(outputStream);
     }
 
     /**
@@ -93,17 +98,15 @@
      * @param transition The transition that has finished.
      */
     public void logFinishedTransition(Transition transition) {
-        if (transition.mLogger.mProtoToken == 0) {
-            // Transition finished but never sent, so open token never added
-            final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream;
-            transition.mLogger.mProtoToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS);
-        }
+        final ProtoOutputStream outputStream = new ProtoOutputStream();
+        final long protoToken = outputStream
+                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+        outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+        outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
+                transition.mLogger.mFinishTimeNs);
+        outputStream.end(protoToken);
 
-        // Dump the rest of the transition's info that wasn't dumped during logSentTransition
-        dumpFinishedTransitionToProto(transition.mLogger.mProtoOutputStream, transition);
-        transition.mLogger.mProtoOutputStream.end(transition.mLogger.mProtoToken);
-        mTraceBuffer.pushTransitionProto(transition.mLogger.mProtoOutputStream);
+        mTraceBuffer.add(outputStream);
     }
 
     /**
@@ -113,38 +116,15 @@
      * @param transition The transition that has been aborted
      */
     public void logAbortedTransition(Transition transition) {
-        // We don't care about aborted transitions unless actively tracing
-        if (!mActiveTracingEnabled) {
-            return;
-        }
-        logFinishedTransition(transition);
-    }
-
-    /**
-     * Records the current state of a transition in the transition trace (if it is running).
-     * @param transition the transition that we want to record the state of.
-     */
-    public void logState(com.android.server.wm.Transition transition) {
-        if (!mActiveTracingEnabled) {
-            return;
-        }
         final ProtoOutputStream outputStream = new ProtoOutputStream();
-        dumpTransitionStateToProto(outputStream, transition);
-        mTraceBuffer.pushTransitionState(outputStream);
-    }
+        final long protoToken = outputStream
+                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+        outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+        outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
+                transition.mLogger.mAbortTimeNs);
+        outputStream.end(protoToken);
 
-    /**
-     * Records the transition info that is being sent over to Shell.
-     * @param transition The transition the info is associated with.
-     * @param info The transition info we want to log.
-     */
-    private void logTransitionInfo(Transition transition, TransitionInfo info) {
-        if (!mActiveTracingEnabled) {
-            return;
-        }
-        final ProtoOutputStream outputStream = new ProtoOutputStream();
-        dumpTransitionInfoToProto(outputStream, transition, info);
-        mTraceBuffer.pushTransitionInfo(outputStream);
+        mTraceBuffer.add(outputStream);
     }
 
     private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
@@ -189,139 +169,6 @@
         Trace.endSection();
     }
 
-    private void dumpFinishedTransitionToProto(
-            ProtoOutputStream outputStream,
-            Transition transition
-    ) {
-        Trace.beginSection("TransitionTracer#dumpFinishedTransitionToProto");
-
-        outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
-                transition.mLogger.mCreateTimeNs);
-        outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
-                transition.mLogger.mSendTimeNs);
-        outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
-                transition.mLogger.mFinishTimeNs);
-
-        Trace.endSection();
-    }
-
-    private void dumpTransitionStateToProto(ProtoOutputStream outputStream, Transition transition) {
-        Trace.beginSection("TransitionTracer#dumpTransitionStateToProto");
-
-        final long stateToken = outputStream
-                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_STATES);
-
-        outputStream.write(com.android.server.wm.shell.TransitionState.TIME_NS,
-                SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_ID,
-                transition.getSyncId());
-        outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_TYPE,
-                transition.mType);
-        outputStream.write(com.android.server.wm.shell.TransitionState.STATE,
-                transition.getState());
-        outputStream.write(com.android.server.wm.shell.TransitionState.FLAGS,
-                transition.getFlags());
-
-        for (int i = 0; i < transition.mChanges.size(); ++i) {
-            final WindowContainer window = transition.mChanges.keyAt(i);
-            final ChangeInfo changeInfo = transition.mChanges.valueAt(i);
-            dumpChangeInfoToProto(outputStream, window, changeInfo);
-        }
-
-        for (int i = 0; i < transition.mParticipants.size(); ++i) {
-            final WindowContainer window = transition.mParticipants.valueAt(i);
-            window.writeIdentifierToProto(outputStream,
-                    com.android.server.wm.shell.TransitionState.PARTICIPANTS);
-        }
-
-        outputStream.end(stateToken);
-        Trace.endSection();
-    }
-
-    private void dumpChangeInfoToProto(ProtoOutputStream outputStream, WindowContainer window,
-            ChangeInfo changeInfo) {
-        Trace.beginSection("TransitionTraceBuffer#writeChange");
-        final long changeEntryToken =
-                outputStream.start(com.android.server.wm.shell.TransitionState.CHANGE);
-
-        final int transitMode = changeInfo.getTransitMode(window);
-        final boolean hasChanged = changeInfo.hasChanged();
-        final int changeFlags = changeInfo.getChangeFlags(window);
-        final int windowingMode = changeInfo.mWindowingMode;
-
-        outputStream.write(com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE, transitMode);
-        outputStream.write(com.android.server.wm.shell.ChangeInfo.HAS_CHANGED, hasChanged);
-        outputStream.write(com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS, changeFlags);
-        outputStream.write(com.android.server.wm.shell.ChangeInfo.WINDOWING_MODE, windowingMode);
-        window.writeIdentifierToProto(
-                outputStream, com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER);
-
-        outputStream.end(changeEntryToken);
-        Trace.endSection();
-    }
-
-    private void dumpTransitionInfoToProto(ProtoOutputStream outputStream,
-            Transition transition, TransitionInfo info) {
-        Trace.beginSection("TransitionTracer#dumpTransitionInfoToProto");
-        final long transitionInfoToken = outputStream
-                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_INFO);
-
-        outputStream.write(com.android.server.wm.shell.TransitionInfo.TRANSITION_ID,
-                transition.getSyncId());
-        for (int i = 0; i < info.getChanges().size(); ++i) {
-            TransitionInfo.Change change = info.getChanges().get(i);
-            dumpTransitionInfoChangeToProto(outputStream, change);
-        }
-
-        outputStream.end(transitionInfoToken);
-        Trace.endSection();
-    }
-
-    private void dumpTransitionInfoChangeToProto(
-            ProtoOutputStream outputStream,
-            TransitionInfo.Change change
-    ) {
-        Trace.beginSection("TransitionTracer#dumpTransitionInfoChangeToProto");
-        final long changeEntryToken = outputStream
-                .start(com.android.server.wm.shell.TransitionInfo.CHANGE);
-
-        outputStream.write(com.android.server.wm.shell.TransitionInfoChange.LAYER_ID,
-                change.getLeash().getLayerId());
-        outputStream.write(com.android.server.wm.shell.TransitionInfoChange.MODE, change.getMode());
-
-        outputStream.end(changeEntryToken);
-        Trace.endSection();
-    }
-
-    private class TransitionTraceBuffer {
-        private final TraceBuffer mTransitionBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
-        private final TraceBuffer mStateBuffer = new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY);
-        private final TraceBuffer mTransitionInfoBuffer =
-                new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY);
-
-        private void pushTransitionProto(ProtoOutputStream outputStream) {
-            mTransitionBuffer.add(outputStream);
-        }
-
-        private void pushTransitionState(ProtoOutputStream outputStream) {
-            mStateBuffer.add(outputStream);
-        }
-
-        private void pushTransitionInfo(ProtoOutputStream outputStream) {
-            mTransitionInfoBuffer.add(outputStream);
-        }
-
-        public void writeToFile(File file, ProtoOutputStream proto) throws IOException {
-            mTransitionBuffer.writeTraceToFile(file, proto);
-        }
-
-        public void reset() {
-            mTransitionBuffer.resetBuffer();
-            mStateBuffer.resetBuffer();
-            mTransitionInfoBuffer.resetBuffer();
-        }
-    }
-
     /**
      * Starts collecting transitions for the trace.
      * If called while a trace is already running, this will reset the trace.
@@ -335,8 +182,8 @@
         LogAndPrintln.i(pw, "Starting shell transition trace.");
         synchronized (mEnabledLock) {
             mActiveTracingEnabled = true;
-            mTraceBuffer.mTransitionBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
-            mTraceBuffer.reset();
+            mTraceBuffer.resetBuffer();
+            mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
         }
         Trace.endSection();
     }
@@ -364,8 +211,8 @@
         synchronized (mEnabledLock) {
             mActiveTracingEnabled = false;
             writeTraceToFileLocked(pw, outputFile);
-            mTraceBuffer.reset();
-            mTraceBuffer.mTransitionBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
+            mTraceBuffer.resetBuffer();
+            mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
         }
         Trace.endSection();
     }
@@ -404,7 +251,7 @@
             int pid = android.os.Process.myPid();
             LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
                     + " from process " + pid);
-            mTraceBuffer.writeToFile(file, proto);
+            mTraceBuffer.writeTraceToFile(file, proto);
         } catch (IOException e) {
             LogAndPrintln.e(pw, "Unable to write buffer to file", e);
         }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 3ccf183..829a33d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3592,11 +3592,11 @@
                 && !mTransitionController.useShellTransitionsRotation()) {
             if (deltaRotation != Surface.ROTATION_0) {
                 updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
-                t.setFixedTransformHint(mSurfaceControl,
+                getPendingTransaction().setFixedTransformHint(mSurfaceControl,
                         getWindowConfiguration().getDisplayRotation());
             } else if (deltaRotation != mLastDeltaRotation) {
                 t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
-                t.unsetFixedTransformHint(mSurfaceControl);
+                getPendingTransaction().unsetFixedTransformHint(mSurfaceControl);
             }
         }
         mLastDeltaRotation = deltaRotation;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8baf048..f253fb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3922,15 +3922,17 @@
 
     /**
      * Returns the touch mode state for the display id passed as argument.
+     *
+     * This method will return the default touch mode state (represented by
+     * {@code com.android.internal.R.bool.config_defaultInTouchMode}) if the display passed as
+     * argument is no longer registered in {@RootWindowContainer}).
      */
     @Override  // Binder call
     public boolean isInTouchMode(int displayId) {
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent == null) {
-                throw new IllegalStateException("Failed to retrieve the touch mode state for"
-                        + "display {" + displayId + "}: display is not registered in "
-                        + "WindowRootContainer");
+                return mContext.getResources().getBoolean(R.bool.config_defaultInTouchMode);
             }
             return displayContent.isInTouchMode();
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 4c5efef..31afcbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -596,7 +596,8 @@
                 .build();
         t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
         t.reparent(getSurfaceControl(), leash);
-        t.setFixedTransformHint(leash, getWindowConfiguration().getDisplayRotation());
+        getPendingTransaction().setFixedTransformHint(leash,
+                getWindowConfiguration().getDisplayRotation());
         mFixedRotationTransformLeash = leash;
         updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
         return mFixedRotationTransformLeash;
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 4b32062..0af5647 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -49,6 +49,7 @@
         ICreateCredentialCallback, CreateCredentialResponse>
         implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
     private static final String TAG = "CreateRequestSession";
+    private final Set<String> mPrimaryProviders;
 
     CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
@@ -56,6 +57,7 @@
             ICreateCredentialCallback callback,
             CallingAppInfo callingAppInfo,
             Set<ComponentName> enabledProviders,
+            Set<String> primaryProviders,
             CancellationSignal cancellationSignal,
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
@@ -63,6 +65,7 @@
                 callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
         mRequestSessionMetric.collectCreateFlowInitialMetricInfo(
                 /*origin=*/request.getOrigin() != null);
+        mPrimaryProviders = primaryProviders;
     }
 
     /**
@@ -99,8 +102,7 @@
                             mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
-                            // TODO(b/279480457): populate
-                            /*defaultProviderId=*/new ArrayList<>()),
+                            /*defaultProviderId=*/new ArrayList<String>(mPrimaryProviders)),
                     providerDataList);
             mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 6793800..86dbe11 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
 import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
-import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
 import static android.content.Context.CREDENTIAL_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
@@ -46,7 +45,6 @@
 import android.credentials.PrepareGetCredentialResponseInternal;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
-import android.credentials.ui.IntentFactory;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.IBinder;
@@ -69,6 +67,7 @@
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -282,6 +281,21 @@
         }
     }
 
+    private static Set<String> getPrimaryProvidersForUserId(Context context, int userId) {
+        final int resolvedUserId = ActivityManager.handleIncomingUser(
+                Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, false,
+                "getPrimaryProvidersForUserId", null);
+        SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver(
+                context, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                /* isMultipleMode= */ true);
+        String[] serviceNames = resolver.readServiceNameList(resolvedUserId);
+        if (serviceNames == null) {
+            return new HashSet<String>();
+        }
+        return new HashSet<String>(Arrays.asList(serviceNames));
+    }
+
     @GuardedBy("mLock")
     private List<CredentialManagerServiceImpl> getCredentialProviderServicesLocked(int userId) {
         List<CredentialManagerServiceImpl> concatenatedServices = new ArrayList<>();
@@ -468,7 +482,7 @@
                             callback,
                             request,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
-                            getEnabledProviders(),
+                            getEnabledProvidersForUser(userId),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan);
             addSessionLocked(userId, session);
@@ -523,7 +537,7 @@
                             getCredentialCallback,
                             request,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
-                            getEnabledProviders(),
+                            getEnabledProvidersForUser(userId),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan,
                             prepareGetCredentialCallback);
@@ -641,7 +655,8 @@
                             request,
                             callback,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
-                            getEnabledProviders(),
+                            getEnabledProvidersForUser(userId),
+                            getPrimaryProvidersForUserId(getContext(), userId),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan);
             addSessionLocked(userId, session);
@@ -689,7 +704,8 @@
 
         @Override
         public void setEnabledProviders(
-                List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
+                List<String>  primaryProviders, List<String> providers, int userId,
+                ISetEnabledProvidersCallback callback) {
             if (!hasWriteSecureSettingsPermission()) {
                 try {
                     callback.onError(
@@ -710,17 +726,27 @@
                             "setEnabledProviders",
                             null);
 
-            String storedValue = String.join(":", providers);
-            if (!Settings.Secure.putStringForUser(
-                    getContext().getContentResolver(),
-                    Settings.Secure.CREDENTIAL_SERVICE,
-                    storedValue,
-                    userId)) {
-                Slog.e(TAG, "Failed to store setting containing enabled providers");
+            Set<String> enableProvider = new HashSet<>(providers);
+            enableProvider.addAll(primaryProviders);
+
+            boolean writeEnabledStatus =
+                    Settings.Secure.putStringForUser(getContext().getContentResolver(),
+                            Settings.Secure.CREDENTIAL_SERVICE,
+                            String.join(":", enableProvider),
+                            userId);
+
+            boolean writePrimaryStatus =
+                    Settings.Secure.putStringForUser(getContext().getContentResolver(),
+                            Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                            String.join(":", primaryProviders),
+                            userId);
+
+            if (!writeEnabledStatus || !writePrimaryStatus) {
+                Slog.e(TAG, "Failed to store setting containing enabled or primary providers");
                 try {
                     callback.onError(
                             "failed_setting_store",
-                            "Failed to store setting containing enabled providers");
+                            "Failed to store setting containing enabled or primary providers");
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Issue with invoking error response: ", e);
                     return;
@@ -734,10 +760,6 @@
                 Slog.e(TAG, "Issue with invoking response: ", e);
                 // TODO: Propagate failure
             }
-
-            // Send an intent to the UI that we have new enabled providers.
-            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
-                    LAUNCH_CREDENTIAL_SELECTOR);
         }
 
         @Override
@@ -785,7 +807,8 @@
             verifyGetProvidersPermission();
 
             return CredentialProviderInfoFactory.getCredentialProviderServices(
-                    mContext, userId, providerFilter, getEnabledProviders());
+                    mContext, userId, providerFilter, getEnabledProvidersForUser(userId),
+                    getPrimaryProvidersForUserId(mContext, userId));
         }
 
         @Override
@@ -795,7 +818,8 @@
 
             final int userId = UserHandle.getCallingUserId();
             return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
-                    mContext, userId, providerFilter, getEnabledProviders());
+                    mContext, userId, providerFilter, getEnabledProvidersForUser(userId),
+                    getPrimaryProvidersForUserId(mContext, userId));
         }
 
         @Override
@@ -811,24 +835,26 @@
             }
         }
 
-        @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
-        // this.mLock
-        private Set<ComponentName> getEnabledProviders() {
+        private Set<ComponentName> getEnabledProvidersForUser(int userId) {
+            final int resolvedUserId = ActivityManager.handleIncomingUser(
+                Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, false,
+                "getEnabledProvidersForUser", null);
+
             Set<ComponentName> enabledProviders = new HashSet<>();
-            synchronized (mLock) {
-                runForUser(
-                        (service) -> {
-                            try {
-                                enabledProviders.add(
-                                        service.getCredentialProviderInfo()
-                                                .getServiceInfo().getComponentName());
-                            } catch (NullPointerException e) {
-                                // Safe check
-                                Slog.e(TAG, "Skipping provider as either the providerInfo"
-                                        + " or serviceInfo is null - weird");
-                            }
-                        });
+            String directValue = Settings.Secure.getStringForUser(
+                mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, resolvedUserId);
+
+            if (!TextUtils.isEmpty(directValue)) {
+                String[] components = directValue.split(":");
+                for (String componentString : components) {
+                    ComponentName component = ComponentName.unflattenFromString(componentString);
+                    if (component != null) {
+                        enabledProviders.add(component);
+                    }
+                }
             }
+
             return enabledProviders;
         }
 
@@ -858,7 +884,7 @@
                             callback,
                             request,
                             constructCallingAppInfo(callingPackage, userId, null),
-                            getEnabledProviders(),
+                            getEnabledProvidersForUser(userId),
                             CancellationSignal.fromTransport(cancelTransport),
                             timestampBegan);
             addSessionLocked(userId, session);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index c2b5102..b3812c9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -36,6 +36,7 @@
 import android.util.Slog;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
@@ -154,7 +155,9 @@
                         mContext,
                         mUserId,
                         CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
-                        mEnabledProviders);
+                        mEnabledProviders,
+                        // Don't need primary providers here.
+                        new HashSet<String>());
 
         List<DisabledProviderData> disabledProviderDataList = allProviders.stream()
                 .filter(provider -> !provider.isEnabled())
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 1503410..e9fa883 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.credentials.CredentialOption;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialRequest;
@@ -52,11 +53,22 @@
             CancellationSignal cancellationSignal,
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
-                RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal,
-                startedTimestamp);
+                getRequestInfoFromRequest(request), callingAppInfo, enabledProviders,
+                cancellationSignal, startedTimestamp);
         mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
     }
 
+    private static String getRequestInfoFromRequest(GetCredentialRequest request) {
+        for (CredentialOption option : request.getCredentialOptions()) {
+            if (option.getCredentialRetrievalData().getStringArrayList(
+                    CredentialOption
+                            .SUPPORTED_ELEMENT_KEYS) != null) {
+                return RequestInfo.TYPE_GET_VIA_REGISTRY;
+            }
+        }
+        return RequestInfo.TYPE_GET;
+    }
+
     /**
      * Creates a new provider session, and adds it list of providers that are contributing to
      * this session.
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 1f0346a..d4b8800 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -132,7 +132,8 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
+            mRemoteCredentialService.setCallback(this);
+            mRemoteCredentialService.onClearCredentialState(mProviderRequest);
         }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 16beaa4..409806a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -248,7 +248,8 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this);
+            mRemoteCredentialService.setCallback(this);
+            mRemoteCredentialService.onBeginCreateCredential(mProviderRequest);
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 1e80703..64438e3 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -309,7 +309,8 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
+            mRemoteCredentialService.setCallback(this);
+            mRemoteCredentialService.onBeginGetCredential(mProviderRequest);
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index f5e3b86..4bcf8be 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -48,6 +48,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -59,13 +60,17 @@
 
     private static final String TAG = "RemoteCredentialService";
     /** Timeout for a single request. */
-    private static final long TIMEOUT_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+    private static final long TIMEOUT_REQUEST_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS;
     /** Timeout to unbind after the task queue is empty. */
     private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS =
             5 * DateUtils.SECOND_IN_MILLIS;
 
     private final ComponentName mComponentName;
 
+    private AtomicBoolean mOngoingRequest = new AtomicBoolean(false);
+
+    @Nullable private ProviderCallbacks mCallback;
+
     /**
      * Callbacks to be invoked when the provider remote service responds with a
      * success or failure.
@@ -94,12 +99,35 @@
         mComponentName = componentName;
     }
 
+    public void setCallback(ProviderCallbacks callback) {
+        mCallback = callback;
+    }
+
     /** Unbinds automatically after this amount of time. */
     @Override
     protected long getAutoDisconnectTimeoutMs() {
         return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS;
     }
 
+    @Override
+    public void onBindingDied(ComponentName name) {
+        super.onBindingDied(name);
+
+        Slog.w(TAG, "binding died for: " + name);
+    }
+
+    @Override
+    public void binderDied() {
+        super.binderDied();
+        Slog.w(TAG, "binderDied");
+
+        if (mCallback != null) {
+            mOngoingRequest.set(false);
+            mCallback.onProviderServiceDied(this);
+        }
+
+    }
+
     /** Return the componentName of the service to be connected. */
     @NonNull
     public ComponentName getComponentName() {
@@ -116,11 +144,14 @@
      * provider service.
      *
      * @param request  the request to be sent to the provider
-     * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderGetSession} class that maintains provider state
      */
-    public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
-            ProviderCallbacks<BeginGetCredentialResponse> callback) {
+    public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request) {
+        if (mCallback == null) {
+            Slog.w(TAG, "Callback is not set");
+            return;
+        }
+        mOngoingRequest.set(true);
+
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
                 new AtomicReference<>();
@@ -154,7 +185,9 @@
                                     dispatchCancellationSignal(cancellation);
                                 } else {
                                     cancellationSink.set(cancellation);
-                                    callback.onProviderCancellable(cancellation);
+                                    if (mCallback != null) {
+                                        mCallback.onProviderCancellable(cancellation);
+                                    }
                                 }
                             }
                         });
@@ -166,7 +199,7 @@
         futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
-                handleExecutionResponse(result, error, cancellationSink, callback)));
+                handleExecutionResponse(result, error, cancellationSink)));
     }
 
     /**
@@ -174,11 +207,14 @@
      * provider service.
      *
      * @param request  the request to be sent to the provider
-     * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderCreateSession} class that maintains provider state
      */
-    public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
-            ProviderCallbacks<BeginCreateCredentialResponse> callback) {
+    public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request) {
+        if (mCallback == null) {
+            Slog.w(TAG, "Callback is not set");
+            return;
+        }
+        mOngoingRequest.set(true);
+
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
                 new AtomicReference<>();
@@ -212,7 +248,9 @@
                                             dispatchCancellationSignal(cancellation);
                                         } else {
                                             cancellationSink.set(cancellation);
-                                            callback.onProviderCancellable(cancellation);
+                                            if (mCallback != null) {
+                                                mCallback.onProviderCancellable(cancellation);
+                                            }
                                         }
                                     }
                                 });
@@ -224,7 +262,7 @@
         futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
-                handleExecutionResponse(result, error, cancellationSink, callback)));
+                handleExecutionResponse(result, error, cancellationSink)));
     }
 
     /**
@@ -232,11 +270,14 @@
      * provider service.
      *
      * @param request  the request to be sent to the provider
-     * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderClearSession} class that maintains provider state
      */
-    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
-            ProviderCallbacks<Void> callback) {
+    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request) {
+        if (mCallback == null) {
+            Slog.w(TAG, "Callback is not set");
+            return;
+        }
+        mOngoingRequest.set(true);
+
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
 
@@ -269,7 +310,9 @@
                                             dispatchCancellationSignal(cancellation);
                                         } else {
                                             cancellationSink.set(cancellation);
-                                            callback.onProviderCancellable(cancellation);
+                                            if (mCallback != null) {
+                                                mCallback.onProviderCancellable(cancellation);
+                                            }
                                         }
                                     }
                                 });
@@ -281,40 +324,58 @@
         futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
-                handleExecutionResponse(result, error, cancellationSink, callback)));
+                handleExecutionResponse(result, error, cancellationSink)));
     }
 
     private <T> void handleExecutionResponse(T result,
             Throwable error,
-            AtomicReference<ICancellationSignal> cancellationSink,
-            ProviderCallbacks<T> callback) {
+            AtomicReference<ICancellationSignal> cancellationSink) {
         if (error == null) {
-            callback.onProviderResponseSuccess(result);
+            if (mCallback != null) {
+                mCallback.onProviderResponseSuccess(result);
+            }
         } else {
             if (error instanceof TimeoutException) {
                 Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName);
+                if (!mOngoingRequest.get()) {
+                    return;
+                }
                 dispatchCancellationSignal(cancellationSink.get());
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_TIMEOUT,
-                        null);
+                if (mCallback != null) {
+                    mOngoingRequest.set(false);
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_TIMEOUT, null);
+                }
             } else if (error instanceof CancellationException) {
                 Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName);
+                if (!mOngoingRequest.get()) {
+                    return;
+                }
                 dispatchCancellationSignal(cancellationSink.get());
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_TASK_CANCELED,
-                        null);
+                if (mCallback != null) {
+                    mOngoingRequest.set(false);
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_TASK_CANCELED,
+                            null);
+                }
             } else if (error instanceof GetCredentialException) {
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
-                        (GetCredentialException) error);
+                if (mCallback != null) {
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+                            (GetCredentialException) error);
+                }
             } else if (error instanceof CreateCredentialException) {
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
-                        (CreateCredentialException) error);
+                if (mCallback != null) {
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+                            (CreateCredentialException) error);
+                }
             } else {
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_UNKNOWN,
-                        (Exception) error);
+                if (mCallback != null) {
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_UNKNOWN,
+                            (Exception) error);
+                }
             }
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index 1930a48..fd49796 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -18,11 +18,13 @@
 
 import static android.credentials.ui.RequestInfo.TYPE_CREATE;
 import static android.credentials.ui.RequestInfo.TYPE_GET;
+import static android.credentials.ui.RequestInfo.TYPE_GET_VIA_REGISTRY;
 import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED;
 
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_VIA_REGISTRY;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN;
 
@@ -35,6 +37,8 @@
 public enum ApiName {
     UNKNOWN(CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN),
     GET_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL),
+    GET_CREDENTIAL_VIA_REGISTRY(
+CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_VIA_REGISTRY),
     CREATE_CREDENTIAL(
             CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL),
     CLEAR_CREDENTIAL(
@@ -52,6 +56,8 @@
                     CREATE_CREDENTIAL.mInnerMetricCode),
             new AbstractMap.SimpleEntry<>(TYPE_GET,
                     GET_CREDENTIAL.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(TYPE_GET_VIA_REGISTRY,
+                    GET_CREDENTIAL_VIA_REGISTRY.mInnerMetricCode),
             new AbstractMap.SimpleEntry<>(TYPE_UNDEFINED,
                     CLEAR_CREDENTIAL.mInnerMetricCode)
     );
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index debfedc..bb3b438 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23200,6 +23200,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEFAULT_SMS,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_INPUT_METHODS,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8030bb7..bac39e0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -265,7 +265,7 @@
                     //  never used, but might need some refactoring to not always assume a non-null
                     //  mechanism.
                     TRUE_MORE_RESTRICTIVE,
-                    POLICY_FLAG_LOCAL_ONLY_POLICY,
+                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
                     PolicyEnforcerCallbacks::setApplicationHidden,
                     new BooleanPolicySerializer());
 
@@ -290,7 +290,7 @@
                     new AccountTypePolicyKey(
                             DevicePolicyIdentifiers.ACCOUNT_MANAGEMENT_DISABLED_POLICY),
                     TRUE_MORE_RESTRICTIVE,
-                    POLICY_FLAG_LOCAL_ONLY_POLICY,
+                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
                     // Nothing is enforced, we just need to store it
                     (Boolean value, Context context, Integer userId, PolicyKey policyKey) -> true,
                     new BooleanPolicySerializer());
@@ -311,7 +311,7 @@
     static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY),
             new MostRecent<>(),
-            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
             (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true,
             new StringSetPolicySerializer());
 
@@ -319,14 +319,14 @@
     static PolicyDefinition<Boolean> SCREEN_CAPTURE_DISABLED = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.SCREEN_CAPTURE_DISABLED_POLICY),
             TRUE_MORE_RESTRICTIVE,
-            /* flags= */ 0,
+            POLICY_FLAG_INHERITABLE,
             PolicyEnforcerCallbacks::setScreenCaptureDisabled,
             new BooleanPolicySerializer());
 
     static PolicyDefinition<Boolean> PERSONAL_APPS_SUSPENDED = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.PERSONAL_APPS_SUSPENDED_POLICY),
             new MostRecent<>(),
-            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
             PolicyEnforcerCallbacks::setPersonalAppsSuspended,
             new BooleanPolicySerializer());
 
@@ -547,7 +547,7 @@
             String restriction, int flags) {
         String identifier = DevicePolicyIdentifiers.getIdentifierForUserRestriction(restriction);
         UserRestrictionPolicyKey key = new UserRestrictionPolicyKey(identifier, restriction);
-        flags |= POLICY_FLAG_USER_RESTRICTION_POLICY;
+        flags |= (POLICY_FLAG_USER_RESTRICTION_POLICY | POLICY_FLAG_INHERITABLE);
         PolicyDefinition<Boolean> definition = new PolicyDefinition<>(
                 key,
                 TRUE_MORE_RESTRICTIVE,
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 906cc83..9e37164 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -93,6 +93,9 @@
     private static final int PLATFORM_VERSION = 20;
     private static final int NEWER_VERSION = 30;
 
+    private static final int DISALLOW_PRERELEASE = -1;
+    private static final int DISALLOW_RELEASED = -1;
+
     @Rule public final Expect expect = Expect.create();
 
     private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename,
@@ -149,8 +152,10 @@
         // Don't allow newer pre-release minSdkVersion on pre-release platform.
         // APP: Pre-release API 30
         // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+                DISALLOW_PRERELEASE);
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                DISALLOW_PRERELEASE);
     }
 
     @Test
@@ -173,21 +178,27 @@
         // Don't allow older pre-release minSdkVersion on released platform.
         // APP: Pre-release API 10
         // DEV: Released API 20
-        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
-        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true,
+                DISALLOW_RELEASED);
+        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                DISALLOW_RELEASED);
 
         // Don't allow same pre-release minSdkVersion on released platform.
         // APP: Pre-release API 20
         // DEV: Released API 20
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true,
+                DISALLOW_RELEASED);
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true,
+                DISALLOW_RELEASED);
 
 
         // Don't allow newer pre-release minSdkVersion on released platform.
         // APP: Pre-release API 30
         // DEV: Released API 20
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true,
+                DISALLOW_RELEASED);
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                DISALLOW_RELEASED);
     }
 
     private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
@@ -254,10 +265,10 @@
         // Don't allow newer pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false,
+                DISALLOW_PRERELEASE);
         verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
-                false, -1
-        );
+                false, DISALLOW_PRERELEASE);
 
         // Do allow newer pre-release targetSdkVersion on pre-release platform when
         // allowUnknownCodenames is true.
@@ -290,35 +301,35 @@
         // Don't allow older pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false,
+                DISALLOW_RELEASED);
         verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
-                false, -1
-        );
+                false, DISALLOW_RELEASED);
 
         // Don't allow same pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false,
+                DISALLOW_RELEASED);
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
-                -1
-        );
+                DISALLOW_RELEASED);
 
         // Don't allow same pre-release targetSdkVersion on released platform when
         // allowUnknownCodenames is true.
         // APP: Pre-release API 20
         // DEV: Released API 20
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
-                -1);
+                DISALLOW_RELEASED);
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
-                -1);
+                DISALLOW_RELEASED);
 
         // Don't allow newer pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false,
+                DISALLOW_RELEASED);
         verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
-                false, -1
-        );
+                false, DISALLOW_RELEASED);
         // Do allow newer pre-release targetSdkVersion on released platform when
         // allowUnknownCodenames is true.
         // APP: Pre-release API 30
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
index e24354f..27a3adc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -90,7 +90,7 @@
     @Test
     public void testParameterValidation() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final int uid = 10123;
         final int pid = 42;
         final int notificationId = 23;
@@ -137,7 +137,7 @@
     @Test
     public void testSingleJob_DetachOnStop() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification = createValidNotification();
         final int uid = 10123;
         final int pid = 42;
@@ -159,7 +159,7 @@
     @Test
     public void testSingleJob_RemoveOnStop() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification = createValidNotification();
         final int uid = 10123;
         final int pid = 42;
@@ -181,7 +181,7 @@
     @Test
     public void testSingleJob_EnqueueDifferentNotificationId_DetachOnStop() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -210,7 +210,7 @@
     @Test
     public void testSingleJob_EnqueueDifferentNotificationId_RemoveOnStop() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -239,7 +239,7 @@
     @Test
     public void testSingleJob_EnqueueSameNotificationId() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -267,8 +267,8 @@
     @Test
     public void testMultipleJobs_sameApp_EnqueueDifferentNotificationId() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc1 = mock(JobServiceContext.class);
-        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final JobServiceContext jsc1 = createJobServiceContext();
+        final JobServiceContext jsc2 = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -313,8 +313,8 @@
     @Test
     public void testMultipleJobs_sameApp_EnqueueSameNotificationId() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc1 = mock(JobServiceContext.class);
-        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final JobServiceContext jsc1 = createJobServiceContext();
+        final JobServiceContext jsc2 = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -355,8 +355,8 @@
     @Test
     public void testMultipleJobs_sameApp_DifferentUsers() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc1 = mock(JobServiceContext.class);
-        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final JobServiceContext jsc1 = createJobServiceContext();
+        final JobServiceContext jsc2 = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid1 = 10123;
@@ -403,8 +403,8 @@
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
         final String pkg1 = "pkg1";
         final String pkg2 = "pkg2";
-        final JobServiceContext jsc1 = mock(JobServiceContext.class);
-        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final JobServiceContext jsc1 = createJobServiceContext();
+        final JobServiceContext jsc2 = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -448,7 +448,7 @@
     @Test
     public void testUserStop_SingleJob_DetachOnStop() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification = createValidNotification();
         final int uid = 10123;
         final int pid = 42;
@@ -470,8 +470,8 @@
     @Test
     public void testUserStop_MultipleJobs_sameApp_EnqueueSameNotificationId_DetachOnStop() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc1 = mock(JobServiceContext.class);
-        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final JobServiceContext jsc1 = createJobServiceContext();
+        final JobServiceContext jsc2 = createJobServiceContext();
         final Notification notification1 = createValidNotification();
         final Notification notification2 = createValidNotification();
         final int uid = 10123;
@@ -512,10 +512,9 @@
     @Test
     public void testUserInitiatedJob_hasNotificationFlag() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
-        final JobStatus js = mock(JobStatus.class);
+        final JobServiceContext jsc = createJobServiceContext();
+        final JobStatus js = jsc.getRunningJobLocked();
         js.startedAsUserInitiatedJob = true;
-        doReturn(js).when(jsc).getRunningJobLocked();
         final Notification notification = createValidNotification();
         final int uid = 10123;
         final int pid = 42;
@@ -532,8 +531,7 @@
     @Test
     public void testNonUserInitiatedJob_doesNotHaveNotificationFlag() {
         final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
-        final JobServiceContext jsc = mock(JobServiceContext.class);
-        doReturn(mock(JobStatus.class)).when(jsc).getRunningJobLocked();
+        final JobServiceContext jsc = createJobServiceContext();
         final Notification notification = createValidNotification();
         final int uid = 10123;
         final int pid = 42;
@@ -547,6 +545,12 @@
         assertEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0);
     }
 
+    private JobServiceContext createJobServiceContext() {
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        doReturn(mock(JobStatus.class)).when(jsc).getRunningJobLocked();
+        return jsc;
+    }
+
     private Notification createValidNotification() {
         final Notification notification = mock(Notification.class);
         doReturn(mock(Icon.class)).when(notification).getSmallIcon();
diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
index 46974cf7..37afc7f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
@@ -217,7 +217,8 @@
     private void registerObserver(IUidObserver observer, int which, int cutpoint,
             String callingPackage, int callingUid) {
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid);
+        mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid,
+                /*uids*/null);
         Mockito.reset(observer);
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index d4a2e9a..66c1e35 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -55,6 +55,7 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -103,6 +104,7 @@
     private RoleManager mRoleManager;
     @Mock
     private Looper mMainLooper;
+    private TestableLooper mTestableLooper;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
@@ -144,9 +146,10 @@
         mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
                 mNotificationInstanceIdSequence);
         mRoleObserver = mService.new RoleObserver(mContext, mRoleManager, mPm, mMainLooper);
+        mTestableLooper = TestableLooper.get(this);
 
         try {
-            mService.init(mService.new WorkerHandler(mock(Looper.class)),
+            mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()),
                     mock(RankingHandler.class),
                     mock(IPackageManager.class), mock(PackageManager.class),
                     mock(LightsManager.class),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index cb41769..2c95bde 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -175,6 +175,7 @@
         mConditionProviders = new ConditionProviders(mContext, new UserProfiles(),
                 AppGlobals.getPackageManager());
         mConditionProviders.addSystemProvider(new CountdownConditionProvider());
+        mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
         mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(),
                 mConditionProviders, mStatsEventBuilderFactory);
 
@@ -816,6 +817,32 @@
     }
 
     @Test
+    public void testSetConfig_updatesAudioEventually() {
+        AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
+        mZenModeHelper.mAudioManager = mAudioManager;
+        setupZenConfig();
+
+        // Change the config a little bit, but enough that it would turn zen mode on
+        ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
+        newConfig.manualRule = new ZenModeConfig.ZenRule();
+        newConfig.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        newConfig.manualRule.enabled = true;
+        mZenModeHelper.setConfig(newConfig, null, "test");
+
+        // audio manager shouldn't do anything until the handler processes its messagse
+        verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
+
+        // now process the looper's messages
+        mTestableLooper.processAllMessages();
+
+        // Expect calls to audio manager
+        verify(mAudioManager, times(1)).updateRingerModeAffectedStreamsInternal();
+
+        // called during applyZenToRingerMode(), which should be true since zen changed
+        verify(mAudioManager, atLeastOnce()).getRingerModeInternal();
+    }
+
+    @Test
     public void testParcelConfig() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelper.mConfig.allowAlarms = false;
@@ -1827,7 +1854,11 @@
 
     @Test
     public void testRulesWithSameUri() {
-        Uri sharedUri = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
+        // needs to be a valid schedule info object for the subscription to happen properly
+        ScheduleInfo scheduleInfo = new ScheduleInfo();
+        scheduleInfo.days = new int[]{1, 2};
+        scheduleInfo.endHour = 1;
+        Uri sharedUri = ZenModeConfig.toScheduleConditionId(scheduleInfo);
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 new ComponentName("android", "ScheduleConditionProvider"),
                 sharedUri,
@@ -1874,14 +1905,12 @@
 
         // note that caller=null because that's how it comes in from NMS.setZenMode
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "");
-        mTestableLooper.processAllMessages();
 
         // confirm that setting zen mode via setManualZenMode changed the zen mode correctly
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
 
         // and also that it works to turn it back off again
         mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "");
-        mTestableLooper.processAllMessages();
         assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
 
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
index e6a1be8..35170b3 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
@@ -28,6 +28,7 @@
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.os.Binder;
 
 import java.util.Arrays;
 import java.util.Locale;
@@ -346,7 +347,8 @@
                         .build(),
                 null /* data */,
                 null /* keyphraseExtras */,
-                12345678 /* halEventReceivedMillis */);
+                12345678 /* halEventReceivedMillis */,
+                new Binder() /* token */);
 
         // Write to a parcel
         Parcel parcel = Parcel.obtain();
@@ -379,7 +381,8 @@
                         .build(),
                 new byte[1] /* data */,
                 kpExtra,
-                12345678 /* halEventReceivedMillis */);
+                12345678 /* halEventReceivedMillis */,
+                new Binder() /* token */);
 
         // Write to a parcel
         Parcel parcel = Parcel.obtain();
@@ -428,7 +431,8 @@
                         .build(),
                 data,
                 kpExtra,
-                12345678 /* halEventReceivedMillis */);
+                12345678 /* halEventReceivedMillis */,
+                new Binder() /* token */);
 
         // Write to a parcel
         Parcel parcel = Parcel.obtain();
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 5a2451f..56bd192 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -164,7 +164,7 @@
     public void testAttachDetach() throws Exception {
         // Normal attachment / detachment.
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
         assertNotNull(module);
         module.detach();
     }
@@ -172,7 +172,7 @@
     @Test
     public void testLoadUnloadModel() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         final int hwHandle = 7;
         int handle = loadGenericModel(module, hwHandle).first;
@@ -183,7 +183,7 @@
     @Test
     public void testLoadPreemptModel() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         final int hwHandle = 7;
         Pair<Integer, SoundTriggerHwCallback> loadResult = loadGenericModel(module, hwHandle);
@@ -202,7 +202,7 @@
     @Test
     public void testLoadUnloadPhraseModel() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         final int hwHandle = 73;
         int handle = loadPhraseModel(module, hwHandle).first;
@@ -213,7 +213,7 @@
     @Test
     public void testStartStopRecognition() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 7;
@@ -225,13 +225,6 @@
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
 
-        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEventSys.class);
-        verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
-        RecognitionEventSys lastEvent = eventCaptor.getValue();
-        assertEquals(-1, lastEvent.halEventReceivedMillis);
-        assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
-
         // Unload the model.
         unloadModel(module, handle, hwHandle);
         module.detach();
@@ -240,7 +233,7 @@
     @Test
     public void testStartRecognitionBusy() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 7;
@@ -264,7 +257,7 @@
     @Test
     public void testStartStopPhraseRecognition() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 67;
@@ -276,13 +269,6 @@
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
 
-        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEventSys.class);
-        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
-        PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
-        assertEquals(-1, lastEvent.halEventReceivedMillis);
-        assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
-
         // Unload the model.
         unloadModel(module, handle, hwHandle);
         module.detach();
@@ -291,7 +277,7 @@
     @Test
     public void testRecognition() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 7;
@@ -336,7 +322,7 @@
     @Test
     public void testPhraseRecognition() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 7;
@@ -366,7 +352,7 @@
     @Test
     public void testForceRecognition() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 17;
@@ -403,7 +389,7 @@
     @Test
     public void testForceRecognitionNotSupported() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 17;
@@ -434,7 +420,7 @@
     @Test
     public void testForcePhraseRecognition() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 17;
@@ -471,7 +457,7 @@
     @Test
     public void testForcePhraseRecognitionNotSupported() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 17;
@@ -503,7 +489,7 @@
     public void testAbortRecognition() throws Exception {
         // Make sure the HAL doesn't support concurrent capture.
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 11;
@@ -535,7 +521,7 @@
     public void testAbortPhraseRecognition() throws Exception {
         // Make sure the HAL doesn't support concurrent capture.
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
 
         // Load the model.
         final int hwHandle = 11;
@@ -566,7 +552,7 @@
     @Test
     public void testParameterSupported() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
         final int hwHandle = 12;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
@@ -588,7 +574,7 @@
     @Test
     public void testParameterNotSupported() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
         final int hwHandle = 13;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
@@ -606,7 +592,7 @@
     @Test
     public void testGetParameter() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
         final int hwHandle = 14;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
@@ -623,7 +609,7 @@
     @Test
     public void testSetParameter() throws Exception {
         ISoundTriggerCallback callback = createCallbackMock();
-        ISoundTriggerModule module = mService.attach(0, callback);
+        ISoundTriggerModule module = mService.attach(0, callback, false);
         final int hwHandle = 17;
         int modelHandle = loadGenericModel(module, hwHandle).first;
 
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index cc357d7..6a1674b 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.verify;
 
@@ -37,7 +38,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FakeLatencyTracker;
@@ -91,18 +91,17 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testSetUpAndTearDown() {
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
             throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
                 ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(),
+                anyBoolean());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
                 RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
@@ -112,12 +111,12 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
                 ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(),
+                anyBoolean());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
                 RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
@@ -132,13 +131,13 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
             throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
                 ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(),
+                anyBoolean());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
                 RecognitionStatus.ABORTED, 100 /* keyphraseId */);
@@ -149,13 +148,13 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
             throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
                 ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback, false);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture(),
+                anyBoolean());
 
         triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
                 RecognitionStatus.SUCCESS);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index bf6901e..9029bc4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -18,6 +18,7 @@
 
 import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
 import static android.view.KeyEvent.KEYCODE_B;
+import static android.view.KeyEvent.KEYCODE_BRIGHTNESS_DOWN;
 import static android.view.KeyEvent.KEYCODE_C;
 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
 import static android.view.KeyEvent.KEYCODE_E;
@@ -186,4 +187,19 @@
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0);
         mPhoneWindowManager.assertGoToHomescreen();
     }
+
+    /**
+     * Sends a KEYCODE_BRIGHTNESS_DOWN event and validates the brightness is decreased as expected;
+     */
+    @Test
+    public void testKeyCodeBrightnessDown() {
+        float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f};
+        float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f};
+
+        for (int i = 0; i < currentBrightness.length; i++) {
+            mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]);
+            sendKey(KEYCODE_BRIGHTNESS_DOWN);
+            mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 1053fd5..d383024 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -89,6 +89,7 @@
 
 import junit.framework.Assert;
 
+import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockSettings;
@@ -339,6 +340,20 @@
         setPhoneCallIsInProgress();
     }
 
+    void prepareBrightnessDecrease(float currentBrightness) {
+        doReturn(0.0f).when(mPowerManager)
+                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+        doReturn(1.0f).when(mPowerManager)
+                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+        doReturn(currentBrightness).when(mDisplayManager)
+                .getBrightness(0);
+    }
+
+    void verifyNewBrightness(float newBrightness) {
+        verify(mDisplayManager).setBrightness(Mockito.eq(0),
+                AdditionalMatchers.eq(newBrightness, 0.001f));
+    }
+
     void setPhoneCallIsInProgress() {
         // Let device has an ongoing phone call.
         doReturn(false).when(mTelecomManager).isRinging();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 353a8ec..8ac6b0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -22,6 +22,7 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -63,6 +64,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -388,6 +390,7 @@
     @SetupWindows(addWindows = { W_ACTIVITY, W_NAVIGATION_BAR })
     @Test
     public void testCanSystemBarsBeShownByUser() {
+        Assume.assumeFalse(CLIENT_TRANSIENT);
         ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true;
         mAppWindow.mAttrs.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
         mAppWindow.setRequestedVisibleTypes(0, navigationBars());
@@ -409,6 +412,7 @@
     @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
     @Test
     public void testTransientBarsSuppressedOnDreams() {
+        Assume.assumeFalse(CLIENT_TRANSIENT);
         final WindowState win = createDreamWindow();
 
         ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 8e80485..04e1d9c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -940,8 +940,6 @@
     @Test
     public void testIgnoresDeskDockRotation_whenNoSensorAndLockedRespected() throws Exception {
         mBuilder.setDeskDockRotation(Surface.ROTATION_270).build();
-        when(mMockDisplayPolicy.isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer())
-                .thenReturn(true);
         configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
 
         when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_DESK);
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 de943d2..06bcbf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -179,44 +179,6 @@
     }
 
     @Test
-    public void testActivityInHistoryAndNotVisibleIsNotUsedAsOpaqueForTranslucentActivities() {
-        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
-        setUpDisplaySizeWithApp(2000, 1000);
-        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = false;
-        // Translucent Activity
-        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
-                .setLaunchedFromUid(mActivity.getUid())
-                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
-                .build();
-        doReturn(false).when(translucentActivity).fillsParent();
-
-        mTask.addChild(translucentActivity);
-
-        assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
-    }
-
-    @Test
-    public void testActivityInHistoryAndVisibleIsUsedAsOpaqueForTranslucentActivities() {
-        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
-        setUpDisplaySizeWithApp(2000, 1000);
-        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = true;
-        // Translucent Activity
-        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
-                .setLaunchedFromUid(mActivity.getUid())
-                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
-                .build();
-        doReturn(false).when(translucentActivity).fillsParent();
-
-        mTask.addChild(translucentActivity);
-
-        assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
-    }
-
-    @Test
     public void testCleanLetterboxConfigListenerWhenTranslucentIsDestroyed() {
         mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
         setUpDisplaySizeWithApp(2000, 1000);
@@ -240,7 +202,6 @@
     public void testHorizontalReachabilityEnabledForTranslucentActivities() {
         setUpDisplaySizeWithApp(2500, 1000);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = true;
         final LetterboxConfiguration config = mWm.mLetterboxConfiguration;
         config.setTranslucentLetterboxingOverrideEnabled(true);
         config.setLetterboxHorizontalPositionMultiplier(0.5f);
@@ -316,7 +277,6 @@
     public void testVerticalReachabilityEnabledForTranslucentActivities() {
         setUpDisplaySizeWithApp(1000, 2500);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = true;
         final LetterboxConfiguration config = mWm.mLetterboxConfiguration;
         config.setTranslucentLetterboxingOverrideEnabled(true);
         config.setLetterboxVerticalPositionMultiplier(0.5f);
@@ -389,13 +349,110 @@
     }
 
     @Test
+    public void testApplyStrategyAgainWhenOpaqueIsDestroyed() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Launch another opaque activity
+        final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        mTask.addChild(opaqueActivity);
+        // Transparent activity strategy not applied
+        assertFalse(opaqueActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
+
+        // Launch translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // Transparent strategy applied
+        assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
+
+        spyOn(translucentActivity.mLetterboxUiController);
+        clearInvocations(translucentActivity.mLetterboxUiController);
+
+        // We destroy the first opaque activity
+        opaqueActivity.setState(DESTROYED, "testing");
+        opaqueActivity.removeImmediately();
+
+        // Check that updateInheritedLetterbox() is invoked again
+        verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox();
+    }
+
+    @Test
+    public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        // Launch translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // Transparent strategy applied
+        assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
+        assertNotNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath);
+
+        spyOn(translucentActivity.mLetterboxUiController);
+        clearInvocations(translucentActivity.mLetterboxUiController);
+
+        // We destroy the first opaque activity
+        mActivity.setState(DESTROYED, "testing");
+        mActivity.removeImmediately();
+
+        // Check that updateInheritedLetterbox() is invoked again
+        verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox();
+        assertNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath);
+    }
+
+    @Test
+    public void testNotApplyStrategyAgainWhenOpaqueIsNotDestroyed() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Launch another opaque activity
+        final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        mTask.addChild(opaqueActivity);
+        // Transparent activity strategy not applied
+        assertFalse(opaqueActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
+
+        // Launch translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // Transparent strategy applied
+        assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
+
+        spyOn(translucentActivity.mLetterboxUiController);
+        clearInvocations(translucentActivity.mLetterboxUiController);
+
+        // Check that updateInheritedLetterbox() is invoked again
+        verify(translucentActivity.mLetterboxUiController, never()).updateInheritedLetterbox();
+    }
+
+    @Test
     public void testApplyStrategyToTranslucentActivities() {
         mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
         setUpDisplaySizeWithApp(2000, 1000);
         prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         mActivity.info.setMinAspectRatio(1.2f);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = true;
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
                 .setLaunchedFromUid(mActivity.getUid())
@@ -456,7 +513,6 @@
         prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         mActivity.info.setMinAspectRatio(1.2f);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = true;
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
                 .setLaunchedFromUid(mActivity.getUid())
@@ -550,7 +606,6 @@
                 true /* ignoreOrientationRequest */);
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
                 1.0f /*letterboxVerticalPositionMultiplier*/);
-        mActivity.nowVisible = true;
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
         // We launch a transparent activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
@@ -583,7 +638,6 @@
         mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
         setUpDisplaySizeWithApp(2800, 1400);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        mActivity.nowVisible = true;
         prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         // Rotate to put activity in size compat mode.
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 58bf184..d3f6818 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -67,7 +67,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.VirtualDisplay;
@@ -515,12 +514,8 @@
 
     @Test
     public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() {
-        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(true).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+        // Enable global touch mode
+        mWm.mPerDisplayFocusEnabled = true;
 
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
@@ -539,12 +534,8 @@
 
     @Test
     public void testSetInTouchMode_nonInstrumentedProcessDontGetPermissionToSwitchTouchMode() {
-        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(true).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+        // Enable global touch mode
+        mWm.mPerDisplayFocusEnabled = true;
 
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
@@ -563,6 +554,9 @@
 
     @Test
     public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
+        // Disable global touch mode
+        mWm.mPerDisplayFocusEnabled = false;
+
         // Create one extra display
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final VirtualDisplay virtualDisplayOwnTouchMode =
@@ -570,17 +564,10 @@
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(3);
         final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
-                        .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
-                        .count();
+                .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+                .count();
         assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
 
-        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(false).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
@@ -598,18 +585,14 @@
 
     @Test
     public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
+        // Enable global touch mode
+        mWm.mPerDisplayFocusEnabled = true;
+
         // Create one extra display
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
-        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(true).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
@@ -628,18 +611,14 @@
 
     @Test
     public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+        // Disable global touch mode
+        mWm.mPerDisplayFocusEnabled = false;
+
         // Create one extra display
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
-        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(false).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
@@ -667,19 +646,14 @@
     }
 
     private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) {
+        // Set global touch mode with the value passed as argument.
+        mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
+
         // Create a couple of extra displays.
         // setInTouchModeOnAllDisplays should ignore the ownFocus setting.
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true);
 
-        // Enable or disable global touch mode (config_perDisplayFocusEnabled setting).
-        // setInTouchModeOnAllDisplays should ignore this value.
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
         doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a4cad5e..863523f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -197,6 +197,8 @@
      */
     private static boolean sOverridesCheckedTestDisplay;
 
+    private boolean mOriginalPerDisplayFocusEnabled;
+
     @BeforeClass
     public static void setUpOnceBase() {
         AttributeCache.init(getInstrumentation().getTargetContext());
@@ -208,6 +210,7 @@
         mSupervisor = mAtm.mTaskSupervisor;
         mRootWindowContainer = mAtm.mRootWindowContainer;
         mWm = mSystemServicesTestRule.getWindowManagerService();
+        mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled;
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
 
         mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
@@ -279,6 +282,7 @@
         if (mUseFakeSettingsProvider) {
             FakeSettingsProvider.clearSettingsProvider();
         }
+        mWm.mPerDisplayFocusEnabled = mOriginalPerDisplayFocusEnabled;
     }
 
     /**
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index b4066ab..00d74bf 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -40,6 +40,7 @@
 import android.os.Binder;
 import android.os.DeadObjectException;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -322,12 +323,31 @@
             modelData.setRunInBatterySaverMode(runInBatterySaverMode);
             modelData.setSoundModel(soundModel);
 
-            if (!isRecognitionAllowedByDeviceState(modelData)) {
-                return STATUS_OK;
+            if (isRecognitionAllowedByDeviceState(modelData)) {
+                int startRecoResult = updateRecognitionLocked(modelData,
+                        false /* Don't notify for synchronous calls */);
+                if (startRecoResult == SoundTrigger.STATUS_OK) {
+                    return startRecoResult;
+                } else if (startRecoResult != SoundTrigger.STATUS_BUSY) {
+                    // If we are returning an unexpected error, don't mark the model as requested
+                    modelData.setRequested(false);
+                    return startRecoResult;
+                }
             }
-
-            return updateRecognitionLocked(modelData,
-                    false /* Don't notify for synchronous calls */);
+            // Either recognition isn't allowed by device state, or the module is busy.
+            // Dispatch a pause.
+            try {
+                if (callback != null) {
+                    mEventLogger.enqueue(new SessionEvent(Type.PAUSE, modelData.getModelId()));
+                    callback.onRecognitionPaused();
+                }
+            } catch (RemoteException e) {
+                mEventLogger.enqueue(new SessionEvent(
+                            Type.PAUSE, modelData.getModelId(), "RemoteException")
+                        .printLog(ALOGW, TAG));
+                forceStopAndUnloadModelLocked(modelData, e);
+            }
+            return STATUS_OK;
         }
     }
 
@@ -468,7 +488,7 @@
             }
         }
 
-        if (unloadModel && (modelData.isModelLoaded() || modelData.isStopPending())) {
+        if (unloadModel && modelData.isModelLoaded()) {
             Slog.d(TAG, "Unloading previously loaded stale model.");
             if (mModule == null) {
                 return STATUS_ERROR;
@@ -769,6 +789,10 @@
             return;
         }
         ModelData model = getModelDataForLocked(event.soundModelHandle);
+        if (!Objects.equals(event.getToken(), model.getToken())) {
+            // Stale event, do nothing
+            return;
+        }
         if (model == null || !model.isGenericModel()) {
             Slog.w(TAG, "Generic recognition event: Model does not exist for handle: "
                     + event.soundModelHandle);
@@ -851,7 +875,11 @@
         Slog.w(TAG, "Recognition aborted");
         MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
         ModelData modelData = getModelDataForLocked(event.soundModelHandle);
-        if (modelData != null && (modelData.isModelStarted() || modelData.isStopPending())) {
+        if (!Objects.equals(event.getToken(), modelData.getToken())) {
+            // Stale event, do nothing
+            return;
+        }
+        if (modelData != null && modelData.isModelStarted()) {
             modelData.setStopped();
             try {
                 IRecognitionStatusCallback callback = modelData.getCallback();
@@ -865,7 +893,6 @@
                         .printLog(ALOGW, TAG));
                 forceStopAndUnloadModelLocked(modelData, e);
             }
-            updateRecognitionLocked(modelData, true);
         }
     }
 
@@ -889,6 +916,10 @@
         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
         int keyphraseId = getKeyphraseIdFromEvent(event);
         ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
+        if (!Objects.equals(event.getToken(), modelData.getToken())) {
+            // Stale event, do nothing
+            return;
+        }
 
         if (modelData == null || !modelData.isKeyphraseModel()) {
             Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
@@ -936,7 +967,7 @@
 
     private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) {
         boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model);
-        if (shouldStartModel == model.isModelStarted() || model.isStopPending()) {
+        if (shouldStartModel == model.isModelStarted()) {
             // No-op.
             return STATUS_OK;
         }
@@ -1041,10 +1072,7 @@
         if (mModule == null) {
             return;
         }
-        if (modelData.isStopPending()) {
-            // No need to wait for the stop to be confirmed.
-            modelData.setStopped();
-        } else if (modelData.isModelStarted()) {
+        if (modelData.isModelStarted()) {
             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
             if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) {
                 modelData.setStopped();
@@ -1188,7 +1216,12 @@
         if (mModule == null) {
             return STATUS_ERROR;
         }
-        int status = mModule.startRecognition(modelData.getHandle(), config);
+        int status = STATUS_OK;
+        try {
+            modelData.setToken(mModule.startRecognitionWithToken(modelData.getHandle(), config));
+        } catch (Exception e) {
+            status = SoundTrigger.handleException(e);
+        }
         if (status != SoundTrigger.STATUS_OK) {
             Slog.w(TAG, "startRecognition failed with " + status);
             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
@@ -1256,7 +1289,7 @@
                 }
             }
         } else {
-            modelData.setStopPending();
+            modelData.setStopped();
             MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
             // Notify of pause if needed.
             if (notify) {
@@ -1303,9 +1336,6 @@
         // Started implies model was successfully loaded and start was called.
         static final int MODEL_STARTED = 2;
 
-        // Model stop request has been sent. Waiting for an event to signal model being stopped.
-        static final int MODEL_STOP_PENDING = 3;
-
         // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
         private int mModelState;
         private UUID mModelId;
@@ -1346,6 +1376,9 @@
         // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
         private SoundModel mSoundModel = null;
 
+        // Token used to disambiguate recognition sessions.
+        private IBinder mRecognitionToken = null;
+
         private ModelData(UUID modelId, int modelType) {
             mModelId = modelId;
             // Private constructor, since we require modelType to be one of TYPE_GENERIC,
@@ -1383,22 +1416,17 @@
             return mModelState == MODEL_NOTLOADED;
         }
 
-        synchronized boolean isStopPending() {
-            return mModelState == MODEL_STOP_PENDING;
-        }
-
         synchronized void setStarted() {
             mModelState = MODEL_STARTED;
         }
 
         synchronized void setStopped() {
+            // If we are moving to the stopped state, we should clear out our
+            // startRecognition token
+            mRecognitionToken = null;
             mModelState = MODEL_LOADED;
         }
 
-        synchronized void setStopPending() {
-            mModelState = MODEL_STOP_PENDING;
-        }
-
         synchronized void setLoaded() {
             mModelState = MODEL_LOADED;
         }
@@ -1467,6 +1495,14 @@
             return mSoundModel;
         }
 
+        synchronized IBinder getToken() {
+            return mRecognitionToken;
+        }
+
+        synchronized void setToken(IBinder token) {
+            mRecognitionToken = token;
+        }
+
         synchronized int getModelType() {
             return mModelType;
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 67320009..a675248 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -303,6 +303,10 @@
 
     private SoundTriggerHelper newSoundTriggerHelper(
             ModuleProperties moduleProperties, EventLogger eventLogger) {
+        return newSoundTriggerHelper(moduleProperties, eventLogger, false);
+    }
+    private SoundTriggerHelper newSoundTriggerHelper(
+            ModuleProperties moduleProperties, EventLogger eventLogger, boolean isTrusted) {
 
         Identity middlemanIdentity = new Identity();
         middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
@@ -325,7 +329,7 @@
                 eventLogger,
                 (SoundTrigger.StatusListener statusListener) -> new SoundTriggerModule(
                         mMiddlewareService, moduleId, statusListener,
-                        Looper.getMainLooper(), middlemanIdentity, originatorIdentity),
+                        Looper.getMainLooper(), middlemanIdentity, originatorIdentity, isTrusted),
                 moduleId,
                 () -> listUnderlyingModuleProperties(originatorIdentity)
                 );
@@ -1724,7 +1728,8 @@
         }
 
         @Override
-        public Session attach(@NonNull IBinder client, ModuleProperties underlyingModule) {
+        public Session attach(@NonNull IBinder client, ModuleProperties underlyingModule,
+                boolean isTrusted) {
             var identity = IdentityContext.getNonNull();
             int sessionId = mSessionIdCounter.getAndIncrement();
             mServiceEventLogger.enqueue(new ServiceEvent(
@@ -1733,7 +1738,7 @@
                     "LocalSoundTriggerEventLogger for package: " +
                     identity.packageName + "#" + sessionId);
 
-            return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger),
+            return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted),
                     client, eventLogger, identity);
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
index 60f89da..ca35b51 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
@@ -38,7 +38,10 @@
      *
      * listModules() must be called prior to calling this method and the provided handle must be
      * one of the handles from the returned list.
+     * @param isTrusted - {@code true} if this service should not note AppOps for recognitions,
+     * and should delegate these checks to the **trusted** client.
      */
     public ISoundTriggerModule attach(int handle,
-            ISoundTriggerCallback callback);
+            ISoundTriggerCallback callback,
+            boolean isTrusted);
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index bac2466..c3e0a3c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -256,7 +256,7 @@
         public void recognitionCallback(int model, RecognitionEventSys event) {
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
-                if (state == null || state == ModelState.INACTIVE) {
+                if (state == null) {
                     Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
@@ -282,7 +282,7 @@
         public void phraseRecognitionCallback(int model, PhraseRecognitionEventSys event) {
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
-                if (state == null || state == ModelState.INACTIVE) {
+                if (state == null) {
                     Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index c8c0f3d..3b800de 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -116,7 +116,8 @@
 
     @Override
     public @NonNull
-    ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+    ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback,
+            boolean isTrusted) {
         return mModules[handle].attach(callback);
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 2ee4e3c..e3366f8 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -149,22 +149,22 @@
 
     @Override
     public @NonNull
-    ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback) {
+    ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback, boolean isTrusted) {
         try {
             var originatorIdentity = IdentityContext.getNonNull();
             String packageIdentification = originatorIdentity.packageName
-                    + mSessionCount.getAndIncrement();
+                    + mSessionCount.getAndIncrement() + (isTrusted ? "trusted" : "");
             ModuleLogging result = new ModuleLogging();
             var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                 "Session logger for: " + packageIdentification);
 
             var callbackWrapper = new CallbackLogging(callback, eventLogger, originatorIdentity);
 
-            result.attach(mDelegate.attach(handle, callbackWrapper), eventLogger);
+            result.attach(mDelegate.attach(handle, callbackWrapper, isTrusted), eventLogger);
 
             mServiceEventLogger.enqueue(ServiceEvent.createForReturn(
                         ServiceEvent.Type.ATTACH,
-                        packageIdentification, result, handle, callback)
+                        packageIdentification, result, handle, callback, isTrusted)
                     .printLog(ALOGI, TAG));
 
             mSessionEventLoggers.add(eventLogger);
@@ -241,13 +241,14 @@
         }
 
         @Override
-        public void startRecognition(int modelHandle, RecognitionConfig config)
+        public IBinder startRecognition(int modelHandle, RecognitionConfig config)
                 throws RemoteException {
             try {
-                mDelegate.startRecognition(modelHandle, config);
-                mEventLogger.enqueue(SessionEvent.createForVoid(
-                            START_RECOGNITION, modelHandle, config)
+                var result = mDelegate.startRecognition(modelHandle, config);
+                mEventLogger.enqueue(SessionEvent.createForReturn(
+                            START_RECOGNITION, result, modelHandle, config)
                         .printLog(ALOGI, TAG));
+                return result;
             } catch (Exception e) {
                 mEventLogger.enqueue(SessionEvent.createForException(
                             START_RECOGNITION, e, modelHandle, config)
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index 00b894e..2e641a2 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -85,11 +85,11 @@
     @Override
     public @NonNull
     ISoundTriggerModule attach(int handle,
-            @NonNull ISoundTriggerCallback callback) {
+            @NonNull ISoundTriggerCallback callback, boolean isTrusted) {
         Identity identity = getIdentity();
         enforcePermissionsForPreflight(identity);
-        ModuleWrapper wrapper = new ModuleWrapper(identity, callback);
-        return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper()));
+        ModuleWrapper wrapper = new ModuleWrapper(identity, callback, isTrusted);
+        return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper(), isTrusted));
     }
 
     // Override toString() in order to have the delegate's ID in it.
@@ -204,11 +204,14 @@
         private ISoundTriggerModule mDelegate;
         private final @NonNull Identity mOriginatorIdentity;
         private final @NonNull CallbackWrapper mCallbackWrapper;
+        private final boolean mIsTrusted;
 
         ModuleWrapper(@NonNull Identity originatorIdentity,
-                @NonNull ISoundTriggerCallback callback) {
+                @NonNull ISoundTriggerCallback callback,
+                boolean isTrusted) {
             mOriginatorIdentity = originatorIdentity;
             mCallbackWrapper = new CallbackWrapper(callback);
+            mIsTrusted = isTrusted;
         }
 
         ModuleWrapper attach(@NonNull ISoundTriggerModule delegate) {
@@ -241,10 +244,10 @@
         }
 
         @Override
-        public void startRecognition(int modelHandle, @NonNull RecognitionConfig config)
+        public IBinder startRecognition(int modelHandle, @NonNull RecognitionConfig config)
                 throws RemoteException {
             enforcePermissions();
-            mDelegate.startRecognition(modelHandle, config);
+            return mDelegate.startRecognition(modelHandle, config);
         }
 
         @Override
@@ -347,7 +350,11 @@
             }
 
             private void enforcePermissions(String reason) {
-                enforcePermissionsForDataDelivery(mOriginatorIdentity, reason);
+                if (mIsTrusted) {
+                    enforcePermissionsForPreflight(mOriginatorIdentity);
+                } else {
+                    enforcePermissionsForDataDelivery(mOriginatorIdentity, reason);
+                }
             }
         }
     }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 91e5466..9de2438 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -34,6 +34,7 @@
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.IBinder;
 import android.os.RemoteException;
 
 import com.android.server.SystemService;
@@ -104,17 +105,17 @@
     public ISoundTriggerModule attachAsOriginator(int handle, Identity identity,
             ISoundTriggerCallback callback) {
         try (SafeCloseable ignored = establishIdentityDirect(Objects.requireNonNull(identity))) {
-            return new ModuleService(mDelegate.attach(handle, callback));
+            return new ModuleService(mDelegate.attach(handle, callback, /* isTrusted= */ false));
         }
     }
 
     @Override
     public ISoundTriggerModule attachAsMiddleman(int handle, Identity middlemanIdentity,
-            Identity originatorIdentity, ISoundTriggerCallback callback) {
+            Identity originatorIdentity, ISoundTriggerCallback callback, boolean isTrusted) {
         try (SafeCloseable ignored = establishIdentityIndirect(
                 Objects.requireNonNull(middlemanIdentity),
                 Objects.requireNonNull(originatorIdentity))) {
-            return new ModuleService(mDelegate.attach(handle, callback));
+            return new ModuleService(mDelegate.attach(handle, callback, isTrusted));
         }
     }
 
@@ -176,10 +177,10 @@
         }
 
         @Override
-        public void startRecognition(int modelHandle, RecognitionConfig config)
+        public IBinder startRecognition(int modelHandle, RecognitionConfig config)
                 throws RemoteException {
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-                mDelegate.startRecognition(modelHandle, config);
+                return mDelegate.startRecognition(modelHandle, config);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index f208c03..31fab89 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -191,7 +191,7 @@
 
     @Override
     public @NonNull ISoundTriggerModule attach(int handle,
-            @NonNull ISoundTriggerCallback callback) {
+            @NonNull ISoundTriggerCallback callback, boolean isTrusted) {
         // Input validation.
         Objects.requireNonNull(callback);
         Objects.requireNonNull(callback.asBinder());
@@ -209,7 +209,7 @@
             // From here on, every exception isn't client's fault.
             try {
                 Session session = new Session(handle, callback);
-                session.attach(mDelegate.attach(handle, session.getCallbackWrapper()));
+                session.attach(mDelegate.attach(handle, session.getCallbackWrapper(), isTrusted));
                 return session;
             } catch (Exception e) {
                 throw handleException(e);
@@ -434,7 +434,7 @@
         }
 
         @Override
-        public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+        public IBinder startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
             // Input validation.
             ValidationUtil.validateRecognitionConfig(config);
 
@@ -458,9 +458,10 @@
 
                 // From here on, every exception isn't client's fault.
                 try {
-                    mDelegate.startRecognition(modelHandle, config);
+                    var result = mDelegate.startRecognition(modelHandle, config);
                     modelState.config = config;
                     modelState.activityState = ModelState.Activity.ACTIVE;
+                    return result;
                 } catch (Exception e) {
                     throw handleException(e);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 84cec55..083211c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -23,7 +23,6 @@
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.SoundModel;
-import android.media.soundtrigger.SoundModelType;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -304,10 +303,10 @@
         }
 
         @Override
-        public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+        public IBinder startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
             synchronized (SoundTriggerModule.this) {
                 checkValid();
-                mLoadedModels.get(modelHandle).startRecognition(config);
+                return mLoadedModels.get(modelHandle).startRecognition(config);
             }
         }
 
@@ -385,8 +384,9 @@
         private class Model implements ISoundTriggerHal.ModelCallback {
             public int mHandle;
             private ModelState mState = ModelState.INIT;
-            private int mType = SoundModelType.INVALID;
             private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
+            private IBinder mRecognitionToken = null;
+            private boolean mIsStopping = false;
 
             private @NonNull
             ModelState getState() {
@@ -402,7 +402,6 @@
                     SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                 mSession = audioSession;
                 mHandle = mHalService.loadSoundModel(model, this);
-                mType = SoundModelType.GENERIC;
                 setState(ModelState.LOADED);
                 mLoadedModels.put(mHandle, this);
                 return mHandle;
@@ -412,7 +411,7 @@
                     SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                 mSession = audioSession;
                 mHandle = mHalService.loadPhraseSoundModel(model, this);
-                mType = SoundModelType.KEYPHRASE;
+
                 setState(ModelState.LOADED);
                 mLoadedModels.put(mHandle, this);
                 return mHandle;
@@ -428,10 +427,15 @@
                 return mSession.mSessionHandle;
             }
 
-            private void startRecognition(@NonNull RecognitionConfig config) {
+            private IBinder startRecognition(@NonNull RecognitionConfig config) {
+                if (mIsStopping == true) {
+                    throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred");
+                }
                 mHalService.startRecognition(mHandle, mSession.mDeviceHandle,
                         mSession.mIoHandle, config);
+                mRecognitionToken = new Binder();
                 setState(ModelState.ACTIVE);
+                return mRecognitionToken;
             }
 
             private void stopRecognition() {
@@ -440,35 +444,13 @@
                         // This call is idempotent in order to avoid races.
                         return;
                     }
+                    mRecognitionToken = null;
+                    mIsStopping = true;
                 }
-                // This must be invoked outside the lock.
                 mHalService.stopRecognition(mHandle);
-
-                // No more callbacks for this model after this point.
                 synchronized (SoundTriggerModule.this) {
-                    // Generate an abortion callback to the client if the model is still active.
-                    if (getState() == ModelState.ACTIVE) {
-                        if (mCallback != null) {
-                            try {
-                                switch (mType) {
-                                    case SoundModelType.GENERIC:
-                                        mCallback.onRecognition(mHandle, AidlUtil.newAbortEvent(),
-                                                mSession.mSessionHandle);
-                                        break;
-                                    case SoundModelType.KEYPHRASE:
-                                        mCallback.onPhraseRecognition(mHandle,
-                                                AidlUtil.newAbortPhraseEvent(),
-                                                mSession.mSessionHandle);
-                                        break;
-                                    default:
-                                        throw new RuntimeException(
-                                                "Unexpected model type: " + mType);
-                                }
-                            } catch (RemoteException e) {
-                            }
-                        }
-                        setState(ModelState.LOADED);
-                    }
+                    mIsStopping = false;
+                    setState(ModelState.LOADED);
                 }
             }
 
@@ -502,9 +484,13 @@
                     @NonNull RecognitionEventSys event) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
+                    if (mRecognitionToken == null) {
+                        return;
+                    }
                     if (!event.recognitionEvent.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
+                    event.token = mRecognitionToken;
                     callback = mCallback;
                 }
                 // The callback must be invoked outside of the lock.
@@ -523,12 +509,15 @@
                     @NonNull PhraseRecognitionEventSys event) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
+                    if (mRecognitionToken == null) {
+                        return;
+                    }
                     if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
+                    event.token = mRecognitionToken;
                     callback = mCallback;
                 }
-
                 // The callback must be invoked outside of the lock.
                 try {
                     if (callback != null) {
@@ -559,5 +548,4 @@
             }
         }
     }
-
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 27f3fb3..423a81a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -381,51 +381,21 @@
                 @NonNull Identity originatorIdentity, IBinder client,
                 ModuleProperties moduleProperties) {
             Objects.requireNonNull(originatorIdentity);
-            boolean forHotwordDetectionService;
+            boolean forHotwordDetectionService = false;
             synchronized (VoiceInteractionManagerServiceStub.this) {
                 enforceIsCurrentVoiceInteractionService();
                 forHotwordDetectionService =
                         mImpl != null && mImpl.mHotwordDetectionConnection != null;
             }
-            IVoiceInteractionSoundTriggerSession session;
-            if (forHotwordDetectionService) {
-                // Use our own identity and handle the permission checks ourselves. This allows
-                // properly checking/noting against the voice interactor or hotword detection
-                // service as needed.
-                if (HotwordDetectionConnection.DEBUG) {
-                    Slog.d(TAG, "Creating a SoundTriggerSession for a HotwordDetectionService");
-                }
-                originatorIdentity.uid = Binder.getCallingUid();
-                originatorIdentity.pid = Binder.getCallingPid();
-                session = new SoundTriggerSessionPermissionsDecorator(
-                        createSoundTriggerSessionForSelfIdentity(client, moduleProperties),
-                        mContext,
-                        originatorIdentity);
-            } else {
-                if (HotwordDetectionConnection.DEBUG) {
-                    Slog.d(TAG, "Creating a SoundTriggerSession");
-                }
-                try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
-                        originatorIdentity)) {
-                    session = new SoundTriggerSession(mSoundTriggerInternal.attach(client,
-                                moduleProperties));
-                }
+            if (HotwordDetectionConnection.DEBUG) {
+                Slog.d(TAG, "Creating a SoundTriggerSession, for HDS: "
+                        + forHotwordDetectionService);
             }
-            return new SoundTriggerSessionBinderProxy(session);
-        }
-
-        private IVoiceInteractionSoundTriggerSession createSoundTriggerSessionForSelfIdentity(
-                IBinder client, ModuleProperties moduleProperties) {
-            Identity identity = new Identity();
-            identity.uid = Process.myUid();
-            identity.pid = Process.myPid();
-            identity.packageName = ActivityThread.currentOpPackageName();
-            return Binder.withCleanCallingIdentity(() -> {
-                try (SafeCloseable ignored = IdentityContext.create(identity)) {
-                    return new SoundTriggerSession(
-                            mSoundTriggerInternal.attach(client, moduleProperties));
-                }
-            });
+            try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
+                    originatorIdentity)) {
+                return new SoundTriggerSession(mSoundTriggerInternal.attach(client,
+                            moduleProperties, forHotwordDetectionService));
+            }
         }
 
         @Override
@@ -1700,11 +1670,7 @@
             return null;
         }
 
-        /**
-         * Implementation of SoundTriggerSession. Does not implement {@link #asBinder()} as it's
-         * intended to be wrapped by an {@link IVoiceInteractionSoundTriggerSession.Stub} object.
-         */
-        private class SoundTriggerSession implements IVoiceInteractionSoundTriggerSession {
+        private class SoundTriggerSession extends IVoiceInteractionSoundTriggerSession.Stub {
             final SoundTriggerInternal.Session mSession;
             private IHotwordRecognitionStatusCallback mSessionExternalCallback;
             private IRecognitionStatusCallback mSessionInternalCallback;
@@ -1851,12 +1817,6 @@
             }
 
             @Override
-            public IBinder asBinder() {
-                throw new UnsupportedOperationException(
-                        "This object isn't intended to be used as a Binder.");
-            }
-
-            @Override
             public void detach() {
                 mSession.detach();
             }
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
index df48cd6..581cd7e 100644
--- a/telecomm/java/android/telecom/CallStreamingService.java
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -73,22 +73,26 @@
         @Override
         public void handleMessage(Message msg) {
             if (mStreamingCallAdapter == null && msg.what != MSG_SET_STREAMING_CALL_ADAPTER) {
+                Log.i(this, "handleMessage: null adapter!");
                 return;
             }
 
             switch (msg.what) {
                 case MSG_SET_STREAMING_CALL_ADAPTER:
                     if (msg.obj != null) {
+                        Log.i(this, "MSG_SET_STREAMING_CALL_ADAPTER");
                         mStreamingCallAdapter = new StreamingCallAdapter(
                                 (IStreamingCallAdapter) msg.obj);
                     }
                     break;
                 case MSG_CALL_STREAMING_STARTED:
+                    Log.i(this, "MSG_CALL_STREAMING_STARTED");
                     mCall = (StreamingCall) msg.obj;
                     mCall.setAdapter(mStreamingCallAdapter);
                     CallStreamingService.this.onCallStreamingStarted(mCall);
                     break;
                 case MSG_CALL_STREAMING_STOPPED:
+                    Log.i(this, "MSG_CALL_STREAMING_STOPPED");
                     mCall = null;
                     mStreamingCallAdapter = null;
                     CallStreamingService.this.onCallStreamingStopped();
@@ -109,6 +113,7 @@
     @Nullable
     @Override
     public IBinder onBind(@NonNull Intent intent) {
+        Log.i(this, "onBind");
         return new CallStreamingServiceBinder();
     }
 
@@ -117,12 +122,14 @@
         @Override
         public void setStreamingCallAdapter(IStreamingCallAdapter streamingCallAdapter)
                 throws RemoteException {
-            mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, mStreamingCallAdapter)
+            Log.i(this, "setCallStreamingAdapter");
+            mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, streamingCallAdapter)
                     .sendToTarget();
         }
 
         @Override
         public void onCallStreamingStarted(StreamingCall call) throws RemoteException {
+            Log.i(this, "onCallStreamingStarted");
             mHandler.obtainMessage(MSG_CALL_STREAMING_STARTED, call).sendToTarget();
         }
 
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index b003f59..331caa1 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -43,8 +43,8 @@
     /** Disconnected because of a local user-initiated action, such as hanging up. */
     public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2
     /**
-     * Disconnected because of a remote user-initiated action, such as the other party hanging up
-     * up.
+     * Disconnected because the remote party hung up an ongoing call, or because an outgoing call
+     * was not answered by the remote party.
      */
     public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3
     /** Disconnected because it has been canceled. */
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
index d4f4322..3319fc1 100644
--- a/telecomm/java/android/telecom/StreamingCall.java
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -55,6 +55,12 @@
     public static final int STATE_DISCONNECTED = 3;
 
     /**
+     * The ID associated with this call.  This is the same value as {@link CallControl#getCallId()}.
+     * @hide
+     */
+    public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
+
+    /**
      * @hide
      */
     private StreamingCall(@NonNull Parcel in) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index fb46ff9..2021ac7 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -278,6 +278,11 @@
      */
     public static final int SATELLITE_REQUEST_IN_PROGRESS = 21;
 
+    /**
+     * Satellite modem is currently busy due to which current request cannot be processed.
+     */
+    public static final int SATELLITE_MODEM_BUSY = 22;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_"}, value = {
             SATELLITE_ERROR_NONE,
@@ -301,7 +306,8 @@
             SATELLITE_NOT_REACHABLE,
             SATELLITE_NOT_AUTHORIZED,
             SATELLITE_NOT_SUPPORTED,
-            SATELLITE_REQUEST_IN_PROGRESS
+            SATELLITE_REQUEST_IN_PROGRESS,
+            SATELLITE_MODEM_BUSY
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteError {}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
index 6b24598..a8f1b3d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
@@ -20,7 +20,6 @@
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.MotionEvent;
@@ -33,7 +32,6 @@
  * Injects gestures given an {@link Instrumentation} object.
  */
 public class GestureHelper {
-    private static final String TAG = GestureHelper.class.getSimpleName();
     // Inserted after each motion event injection.
     private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
 
@@ -210,9 +208,6 @@
             for (int j = 0; j < coords.length; j++) {
                 coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
                 coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
-
-                // TODO: remove logging once b/269505548 is resolved
-                Log.d(TAG, "(" + coords[j].x + ", " + coords[j].y + ")");
             }
 
             eventTime = SystemClock.uptimeMillis();
diff --git a/tests/SilkFX/res/layout/gainmap_metadata.xml b/tests/SilkFX/res/layout/gainmap_metadata.xml
index 0dabaca..4cc3e0c 100644
--- a/tests/SilkFX/res/layout/gainmap_metadata.xml
+++ b/tests/SilkFX/res/layout/gainmap_metadata.xml
@@ -21,8 +21,8 @@
     android:layout_height="wrap_content">
 
     <LinearLayout
-        android:layout_width="350dp"
-        android:layout_height="300dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_centerHorizontal="true"
         android:padding="8dp"
         android:orientation="vertical"
diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
index 6a8752a..501b9d3 100644
--- a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
+++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
@@ -79,6 +79,16 @@
             .isEqualTo(oldCount + 1)
     }
 
+    @Test
+    fun reportUserMayRequestUnlock_differentUserId_doesNotPropagateToAgent() {
+        val oldCount = trustAgentRule.agent.onUserMayRequestUnlockCallCount
+        trustManager.reportUserMayRequestUnlock(userId + 1)
+        await()
+
+        assertThat(trustAgentRule.agent.onUserMayRequestUnlockCallCount)
+            .isEqualTo(oldCount)
+    }
+
     companion object {
         private const val TAG = "UserUnlockRequestTest"
         private fun await() = Thread.sleep(250)
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 2ce2167..1b1e93bd 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -209,6 +209,11 @@
     AddOptionalFlag("--compile-sdk-version-name",
         "Version name to inject into the AndroidManifest.xml if none is present.",
         &options_.manifest_fixer_options.compile_sdk_version_codename);
+    AddOptionalSwitch(
+        "--no-compile-sdk-metadata",
+        "Suppresses output of compile SDK-related attributes in AndroidManifest.xml,\n"
+        "including android:compileSdkVersion and platformBuildVersion.",
+        &options_.manifest_fixer_options.no_compile_sdk_metadata);
     AddOptionalFlagList("--fingerprint-prefix", "Fingerprint prefix to add to install constraints.",
                         &options_.manifest_fixer_options.fingerprint_prefixes);
     AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 53f0abe..c4f6e70 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -719,7 +719,7 @@
     root->InsertChild(0, std::move(uses_sdk));
   }
 
-  if (options_.compile_sdk_version) {
+  if (!options_.no_compile_sdk_metadata && options_.compile_sdk_version) {
     xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersion");
 
     // Make sure we un-compile the value if it was set to something else.
@@ -731,10 +731,9 @@
     // Make sure we un-compile the value if it was set to something else.
     attr->compiled_value = {};
     attr->value = options_.compile_sdk_version.value();
-
   }
 
-  if (options_.compile_sdk_version_codename) {
+  if (!options_.no_compile_sdk_metadata && options_.compile_sdk_version_codename) {
     xml::Attribute* attr =
         root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename");
 
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 175ab6f..42938a4 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -67,11 +67,12 @@
   std::optional<std::string> revision_code_default;
 
   // The version of the framework being compiled against to set for 'android:compileSdkVersion' in
-  // the <manifest> tag.
+  // the <manifest> tag. Not used if no_compile_sdk_metadata is set.
   std::optional<std::string> compile_sdk_version;
 
   // The version codename of the framework being compiled against to set for
-  // 'android:compileSdkVersionCodename' in the <manifest> tag.
+  // 'android:compileSdkVersionCodename' in the <manifest> tag. Not used if no_compile_sdk_metadata
+  // is set.
   std::optional<std::string> compile_sdk_version_codename;
 
   // The fingerprint prefixes to be added to the <install-constraints> tag.
@@ -87,6 +88,9 @@
 
   // Whether to replace the manifest version with the the command line version
   bool replace_version = false;
+
+  // Whether to suppress `android:compileSdkVersion*` and `platformBuildVersion*` attributes.
+  bool no_compile_sdk_metadata = false;
 };
 
 // Verifies that the manifest is correctly formed and inserts defaults where specified with
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 1b8f05b..6151a8e 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -892,6 +892,35 @@
   EXPECT_THAT(attr->value, StrEq("P"));
 }
 
+TEST_F(ManifestFixerTest, DoNotInsertCompileSdkVersions) {
+  std::string input = R"(<manifest package="com.pkg" />)";
+  ManifestFixerOptions options;
+  options.no_compile_sdk_metadata = true;
+  options.compile_sdk_version = {"28"};
+  options.compile_sdk_version_codename = {"P"};
+
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+
+  // There should be a declaration of kSchemaAndroid, even when the input
+  // didn't have one.
+  EXPECT_EQ(manifest->root->namespace_decls.size(), 1);
+  EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android");
+  EXPECT_EQ(manifest->root->namespace_decls[0].uri, xml::kSchemaAndroid);
+
+  xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion");
+  ASSERT_THAT(attr, IsNull());
+
+  attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename");
+  ASSERT_THAT(attr, IsNull());
+
+  attr = manifest->root->FindAttribute("", "platformBuildVersionCode");
+  ASSERT_THAT(attr, IsNull());
+
+  attr = manifest->root->FindAttribute("", "platformBuildVersionName");
+  ASSERT_THAT(attr, IsNull());
+}
+
 TEST_F(ManifestFixerTest, OverrideCompileSdkVersions) {
   std::string input = R"(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"