Merge "[DO NOT MERGE] Compatibilize UDC dev to master" into udc-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index f6c18d6..7f02cb3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -397,18 +397,11 @@
     public static final int FLAG_EXPEDITED = 1 << 4;
 
     /**
-     * Whether it's a data transfer job or not.
-     *
-     * @hide
-     */
-    public static final int FLAG_DATA_TRANSFER = 1 << 5;
-
-    /**
      * Whether it's a user initiated job or not.
      *
      * @hide
      */
-    public static final int FLAG_USER_INITIATED = 1 << 6;
+    public static final int FLAG_USER_INITIATED = 1 << 5;
 
     /**
      * @hide
@@ -738,13 +731,6 @@
     }
 
     /**
-     * @see JobInfo.Builder#setDataTransfer(boolean)
-     */
-    public boolean isDataTransfer() {
-        return (flags & FLAG_DATA_TRANSFER) != 0;
-    }
-
-    /**
      * @see JobInfo.Builder#setUserInitiated(boolean)
      */
     public boolean isUserInitiated() {
@@ -1850,39 +1836,6 @@
         }
 
         /**
-         * Indicates that this job will be used to transfer data to or from a remote server. The
-         * system could attempt to run a data transfer job longer than a regular job if the data
-         * being transferred is potentially very large and can take a long time to complete.
-         *
-         * <p>
-         * You must provide an estimate of the payload size via
-         * {@link #setEstimatedNetworkBytes(long, long)} when scheduling the job or use
-         * {@link JobService#updateEstimatedNetworkBytes(JobParameters, long, long)} or
-         * {@link JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)}
-         * shortly after the job starts.
-         *
-         * <p>
-         * For user-initiated transfers that must be started immediately, call
-         * {@link #setUserInitiated(boolean) setUserInitiated(true)}. Otherwise, the system may
-         * defer the job to a more opportune time.
-         *
-         * <p>
-         * If you want to perform more than one data transfer job, consider enqueuing multiple
-         * {@link JobWorkItem JobWorkItems} along with {@link #setDataTransfer(boolean)}.
-         *
-         * @see JobInfo#isDataTransfer()
-         */
-        @NonNull
-        public Builder setDataTransfer(boolean dataTransfer) {
-            if (dataTransfer) {
-                mFlags |= FLAG_DATA_TRANSFER;
-            } else {
-                mFlags &= (~FLAG_DATA_TRANSFER);
-            }
-            return this;
-        }
-
-        /**
          * Indicates that this job is being scheduled to fulfill an explicit user request.
          * As such, user-initiated jobs can only be scheduled when the app is in the foreground
          * or in a state where launching an activity is allowed, as defined
@@ -1909,6 +1862,11 @@
          * {@link SecurityException}.
          *
          * <p>
+         * In {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, user-initiated jobs can only
+         * be used for network data transfers. As such, they must specify a required network via
+         * {@link #setRequiredNetwork(NetworkRequest)} or {@link #setRequiredNetworkType(int)}.
+         *
+         * <p>
          * These jobs will not be subject to quotas and will be started immediately once scheduled
          * if all constraints are met and the device system health allows for additional tasks.
          *
@@ -2179,10 +2137,6 @@
             if (isPeriodic) {
                 throw new IllegalArgumentException("An expedited job cannot be periodic");
             }
-            if ((flags & FLAG_DATA_TRANSFER) != 0) {
-                throw new IllegalArgumentException(
-                        "An expedited job cannot also be a data transfer job");
-            }
             if (isUserInitiated) {
                 throw new IllegalArgumentException("An expedited job cannot be user-initiated");
             }
@@ -2202,24 +2156,6 @@
             }
         }
 
-        if ((flags & FLAG_DATA_TRANSFER) != 0) {
-            if (backoffPolicy == BACKOFF_POLICY_LINEAR) {
-                throw new IllegalArgumentException(
-                        "A data transfer job cannot have a linear backoff policy.");
-            }
-            if (hasLateConstraint) {
-                throw new IllegalArgumentException("A data transfer job cannot have a deadline");
-            }
-            if ((flags & FLAG_PREFETCH) != 0) {
-                throw new IllegalArgumentException(
-                        "A data transfer job cannot also be a prefetch job");
-            }
-            if (networkRequest == null) {
-                throw new IllegalArgumentException(
-                        "A data transfer job must specify a valid network type");
-            }
-        }
-
         if (isUserInitiated) {
             if (hasEarlyConstraint) {
                 throw new IllegalArgumentException("A user-initiated job cannot have a time delay");
@@ -2245,6 +2181,15 @@
                 throw new IllegalArgumentException(
                         "Can't call addTriggerContentUri() on a user-initiated job");
             }
+            // UIDTs
+            if (networkRequest == null) {
+                throw new IllegalArgumentException(
+                        "A user-initaited data transfer job must specify a valid network type");
+            }
+            if (backoffPolicy == BACKOFF_POLICY_LINEAR) {
+                throw new IllegalArgumentException(
+                        "A user-initiated data transfer job cannot have a linear backoff policy.");
+            }
         }
     }
 
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 bf3789f..0af191a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -478,8 +478,6 @@
                         case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
                         case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
                         case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
-                        case Constants.KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS:
-                        case Constants.KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS:
                         case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
                         case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
                         case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
@@ -574,10 +572,6 @@
                 "runtime_free_quota_max_limit_ms";
         private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms";
         private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
-        private static final String KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
-                "runtime_min_data_transfer_guarantee_ms";
-        private static final String KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS =
-                "runtime_data_transfer_limit_ms";
         private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
                 "runtime_min_user_initiated_guarantee_ms";
         private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
@@ -616,10 +610,6 @@
         public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
         @VisibleForTesting
         public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
-        public static final long DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
-                DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
-        public static final long DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS =
-                DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
         public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
                 Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
         public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
@@ -739,18 +729,6 @@
         public long RUNTIME_MIN_EJ_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS;
 
         /**
-         * The minimum amount of time we try to guarantee normal data transfer jobs will run for.
-         */
-        public long RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
-                DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
-
-        /**
-         * The maximum amount of time we will let a normal data transfer job run for. This will only
-         * apply if there are no other limits that apply to the specific data transfer job.
-         */
-        public long RUNTIME_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS;
-
-        /**
          * The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
          */
         public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
@@ -885,8 +863,6 @@
                     KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
                     KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
-                    KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                    KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
                     KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
                     KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
                     KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
@@ -904,17 +880,6 @@
                     properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                             DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
             // Make sure min runtime is at least as long as regular jobs.
-            RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
-                    properties.getLong(
-                            KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                            DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS));
-            // Max limit should be at least the min guarantee AND the free quota.
-            RUNTIME_DATA_TRANSFER_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
-                    Math.max(RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                            properties.getLong(
-                                    KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
-                                    DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS)));
-            // Make sure min runtime is at least as long as regular jobs.
             RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
                     properties.getLong(
                             KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
@@ -993,10 +958,6 @@
             pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
             pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                     .println();
-            pw.print(KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                    RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS).println();
-            pw.print(KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
-                    RUNTIME_DATA_TRANSFER_LIMIT_MS).println();
             pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
                     RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
             pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
@@ -3291,7 +3252,7 @@
             if (job.shouldTreatAsUserInitiatedJob()
                     && checkRunUserInitiatedJobsPermission(
                             job.getSourceUid(), job.getSourcePackageName())) {
-                if (job.getJob().isDataTransfer()) {
+                if (job.getJob().getRequiredNetwork() != null) { // UI+DT
                     final long estimatedTransferTimeMs =
                             mConnectivityController.getEstimatedTransferTimeMs(job);
                     if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
@@ -3308,9 +3269,6 @@
                             ));
                 }
                 return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
-            } else if (job.getJob().isDataTransfer()) {
-                // For now, don't increase a bg data transfer's minimum guarantee.
-                return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
             } else if (job.shouldTreatAsExpeditedJob()) {
                 // Don't guarantee RESTRICTED jobs more than 5 minutes.
                 return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3328,7 +3286,7 @@
             final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob()
                     && checkRunUserInitiatedJobsPermission(
                             job.getSourceUid(), job.getSourcePackageName());
-            if (job.getJob().isDataTransfer() && allowLongerJob) { // UI+DT
+            if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT
                 return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
             }
             if (allowLongerJob) { // UI with LRJ permission
@@ -3337,9 +3295,6 @@
             if (job.shouldTreatAsUserInitiatedJob()) {
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
             }
-            if (job.getJob().isDataTransfer()) {
-                return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
-            }
             return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     mConstants.USE_TARE_POLICY
                             ? mTareController.getMaxJobExecutionTimeMsLocked(job)
diff --git a/core/api/current.txt b/core/api/current.txt
index 9f3ceb3..8a6bb7b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8733,7 +8733,6 @@
     method public long getTriggerContentMaxDelay();
     method public long getTriggerContentUpdateDelay();
     method @Nullable public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
-    method public boolean isDataTransfer();
     method public boolean isExpedited();
     method public boolean isImportantWhileForeground();
     method public boolean isPeriodic();
@@ -8770,7 +8769,6 @@
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
     method public android.app.job.JobInfo.Builder setClipData(@Nullable android.content.ClipData, int);
-    method @NonNull public android.app.job.JobInfo.Builder setDataTransfer(boolean);
     method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long, long);
     method @NonNull public android.app.job.JobInfo.Builder setExpedited(boolean);
     method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
@@ -9576,8 +9574,6 @@
 
   public final class VirtualDeviceManager {
     method @NonNull public java.util.List<android.companion.virtual.VirtualDevice> getVirtualDevices();
-    field public static final int DEVICE_ID_DEFAULT = 0; // 0x0
-    field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff
   }
 
 }
@@ -10354,6 +10350,8 @@
     field public static final int CONTEXT_RESTRICTED = 4; // 0x4
     field public static final String CREDENTIAL_SERVICE = "credential";
     field public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+    field public static final int DEVICE_ID_DEFAULT = 0; // 0x0
+    field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff
     field public static final String DEVICE_LOCK_SERVICE = "device_lock";
     field public static final String DEVICE_POLICY_SERVICE = "device_policy";
     field public static final String DISPLAY_HASH_SERVICE = "display_hash";
@@ -12448,7 +12446,7 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12458,7 +12456,7 @@
     method public abstract int getApplicationEnabledSetting(@NonNull String);
     method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull android.content.pm.ApplicationInfo);
     method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
@@ -12470,10 +12468,10 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
     method public void getGroupOfPlatformPermission(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.String>);
     method @NonNull public android.content.pm.InstallSourceInfo getInstallSourceInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+    method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags);
     method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
     method @NonNull public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String);
     method @NonNull public abstract byte[] getInstantAppCookie();
@@ -12485,20 +12483,20 @@
     method @NonNull public java.util.Set<java.lang.String> getMimeGroup(@NonNull String);
     method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract String getNameForUid(int);
-    method @Deprecated @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+    method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
     method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method public abstract int[] getPackageGids(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public int[] getPackageGids(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.pm.PackageInstaller getPackageInstaller();
-    method @Deprecated public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract String[] getPackagesForUid(int);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+    method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
     method @NonNull public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @NonNull public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12507,17 +12505,17 @@
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
     method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+    method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
     method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Nullable public android.os.Bundle getSuspendedPackageAppExtras();
     method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String);
@@ -12546,18 +12544,18 @@
     method public abstract boolean isSafeMode();
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
     method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
     method @NonNull public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
     method @NonNull public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(@NonNull String, int);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
     method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
     method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable java.util.List<android.content.Intent>, @NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
     method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
     method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
     method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@Nullable String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryProviderProperty(@NonNull String);
@@ -12568,11 +12566,11 @@
     method public abstract void removePermission(@NonNull String);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
     method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
+    method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
     method @Nullable public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
-    method @Deprecated @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
+    method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
     method @Nullable public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
-    method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+    method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
     method @Nullable public android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
     method public abstract void setApplicationCategoryHint(@NonNull String, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
@@ -19370,7 +19368,7 @@
   public final class OutputConfiguration implements android.os.Parcelable {
     ctor public OutputConfiguration(@NonNull android.view.Surface);
     ctor public OutputConfiguration(int, @NonNull android.view.Surface);
-    ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+    ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
     method public void addSensorPixelModeUsed(int);
     method public void addSurface(@NonNull android.view.Surface);
     method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
@@ -42066,6 +42064,7 @@
   }
 
   public final class CallControl {
+    method public void answer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 47d9ab6..77fb8d0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -588,6 +588,7 @@
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
     method @RequiresPermission(value="android.permission.WATCH_APPOPS", conditional=true) public void startWatchingNoted(@NonNull String[], @NonNull android.app.AppOpsManager.OnOpNotedListener);
+    method @RequiresPermission(value="android.permission.WATCH_APPOPS", conditional=true) public void startWatchingNoted(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpNotedListener);
     method public void stopWatchingNoted(@NonNull android.app.AppOpsManager.OnOpNotedListener);
     field public static final int HISTORY_FLAGS_ALL = 3; // 0x3
     field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1
@@ -3864,15 +3865,15 @@
     method @NonNull public boolean canUserUninstall(@NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_METADATA) public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.dex.ArtManager getArtManager();
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public CharSequence getHarmfulAppWarning(@NonNull String);
     method @Nullable public String getIncidentReportApproverPackageName();
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(@NonNull android.content.pm.PackageManager.PackageInfoFlags, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract android.graphics.drawable.Drawable getInstantAppIcon(String);
     method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent();
@@ -3886,13 +3887,13 @@
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
     method public abstract void registerDexModule(@NonNull String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
@@ -10075,6 +10076,11 @@
     method public boolean connectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork);
     method public boolean disconnectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork);
     method public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
+    method @Nullable public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus getKnownNetworkConnectionStatus();
+    method @NonNull public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks();
+    method @Nullable public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState getSettingsState();
+    method @Nullable public android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus getTetherNetworkConnectionStatus();
+    method @NonNull public java.util.List<android.net.wifi.sharedconnectivity.app.TetherNetwork> getTetherNetworks();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
     method public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 640506d..3e23062 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -145,7 +145,8 @@
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
     field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL
     field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
-    field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
+    field @Deprecated public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
+    field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
     field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -930,7 +931,7 @@
     method @Nullable public String getDefaultTextClassifierPackageName();
     method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken();
     method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
-    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
     method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int);
     method @Nullable public abstract String[] getNamesForUids(int[]);
     method @NonNull public String getPermissionControllerPackageName();
@@ -3546,6 +3547,8 @@
   public final class InputMethodInfo implements android.os.Parcelable {
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
+    field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
+    field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14
   }
 
   public final class InputMethodManager {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8b6d7cb..ba36d93 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -751,9 +751,17 @@
     @SystemApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
 
-    /** @hide Process can access network despite any power saving resrictions */
+    /** @hide Process can access network despite any power saving restrictions */
     @TestApi
-    public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3;
+    public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 1 << 3;
+    /**
+     * @hide
+     * @deprecated Use {@link #PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK} instead.
+     */
+    @TestApi
+    @Deprecated
+    public static final int PROCESS_CAPABILITY_NETWORK =
+            PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
 
     /**
      * Flag used to indicate whether an app is allowed to start a foreground service from the
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b50245d..b0929b5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4621,7 +4621,7 @@
                     ActivityManager.getService());
             if (!service.isUiContext()) { // WindowProviderService is a UI Context.
                 VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
-                if (mLastReportedDeviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT
+                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
                         || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
                     service.updateDeviceId(mLastReportedDeviceId);
                 }
@@ -6149,7 +6149,7 @@
 
     private void updateDeviceIdForNonUIContexts(int deviceId) {
         // Invalid device id is treated as a no-op.
-        if (deviceId == VirtualDeviceManager.DEVICE_ID_INVALID) {
+        if (deviceId == Context.DEVICE_ID_INVALID) {
             return;
         }
         if (deviceId == mLastReportedDeviceId) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 40ed269..9bf9e0c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -1464,6 +1465,149 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final int _NUM_OP = 134;
 
+    /**
+     * All app ops represented as strings.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = { "OPSTR_" }, value = {
+            OPSTR_COARSE_LOCATION,
+            OPSTR_FINE_LOCATION,
+            OPSTR_MONITOR_LOCATION,
+            OPSTR_MONITOR_HIGH_POWER_LOCATION,
+            OPSTR_GET_USAGE_STATS,
+            OPSTR_ACTIVATE_VPN,
+            OPSTR_READ_CONTACTS,
+            OPSTR_WRITE_CONTACTS,
+            OPSTR_READ_CALL_LOG,
+            OPSTR_WRITE_CALL_LOG,
+            OPSTR_READ_CALENDAR,
+            OPSTR_WRITE_CALENDAR,
+            OPSTR_CALL_PHONE,
+            OPSTR_READ_SMS,
+            OPSTR_RECEIVE_SMS,
+            OPSTR_RECEIVE_MMS,
+            OPSTR_RECEIVE_WAP_PUSH,
+            OPSTR_SEND_SMS,
+            OPSTR_CAMERA,
+            OPSTR_RECORD_AUDIO,
+            OPSTR_READ_PHONE_STATE,
+            OPSTR_ADD_VOICEMAIL,
+            OPSTR_USE_SIP,
+            OPSTR_PROCESS_OUTGOING_CALLS,
+            OPSTR_USE_FINGERPRINT,
+            OPSTR_BODY_SENSORS,
+            OPSTR_READ_CELL_BROADCASTS,
+            OPSTR_MOCK_LOCATION,
+            OPSTR_READ_EXTERNAL_STORAGE,
+            OPSTR_WRITE_EXTERNAL_STORAGE,
+            OPSTR_SYSTEM_ALERT_WINDOW,
+            OPSTR_WRITE_SETTINGS,
+            OPSTR_GET_ACCOUNTS,
+            OPSTR_READ_PHONE_NUMBERS,
+            OPSTR_PICTURE_IN_PICTURE,
+            OPSTR_INSTANT_APP_START_FOREGROUND,
+            OPSTR_ANSWER_PHONE_CALLS,
+            OPSTR_ACCEPT_HANDOVER,
+            OPSTR_GPS,
+            OPSTR_VIBRATE,
+            OPSTR_WIFI_SCAN,
+            OPSTR_POST_NOTIFICATION,
+            OPSTR_NEIGHBORING_CELLS,
+            OPSTR_WRITE_SMS,
+            OPSTR_RECEIVE_EMERGENCY_BROADCAST,
+            OPSTR_READ_ICC_SMS,
+            OPSTR_WRITE_ICC_SMS,
+            OPSTR_ACCESS_NOTIFICATIONS,
+            OPSTR_PLAY_AUDIO,
+            OPSTR_READ_CLIPBOARD,
+            OPSTR_WRITE_CLIPBOARD,
+            OPSTR_TAKE_MEDIA_BUTTONS,
+            OPSTR_TAKE_AUDIO_FOCUS,
+            OPSTR_AUDIO_MASTER_VOLUME,
+            OPSTR_AUDIO_VOICE_VOLUME,
+            OPSTR_AUDIO_RING_VOLUME,
+            OPSTR_AUDIO_MEDIA_VOLUME,
+            OPSTR_AUDIO_ALARM_VOLUME,
+            OPSTR_AUDIO_NOTIFICATION_VOLUME,
+            OPSTR_AUDIO_BLUETOOTH_VOLUME,
+            OPSTR_WAKE_LOCK,
+            OPSTR_MUTE_MICROPHONE,
+            OPSTR_TOAST_WINDOW,
+            OPSTR_PROJECT_MEDIA,
+            OPSTR_WRITE_WALLPAPER,
+            OPSTR_ASSIST_STRUCTURE,
+            OPSTR_ASSIST_SCREENSHOT,
+            OPSTR_TURN_SCREEN_ON,
+            OPSTR_RUN_IN_BACKGROUND,
+            OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
+            OPSTR_REQUEST_INSTALL_PACKAGES,
+            OPSTR_RUN_ANY_IN_BACKGROUND,
+            OPSTR_CHANGE_WIFI_STATE,
+            OPSTR_REQUEST_DELETE_PACKAGES,
+            OPSTR_BIND_ACCESSIBILITY_SERVICE,
+            OPSTR_MANAGE_IPSEC_TUNNELS,
+            OPSTR_START_FOREGROUND,
+            OPSTR_BLUETOOTH_SCAN,
+            OPSTR_BLUETOOTH_CONNECT,
+            OPSTR_BLUETOOTH_ADVERTISE,
+            OPSTR_USE_BIOMETRIC,
+            OPSTR_ACTIVITY_RECOGNITION,
+            OPSTR_SMS_FINANCIAL_TRANSACTIONS,
+            OPSTR_READ_MEDIA_AUDIO,
+            OPSTR_WRITE_MEDIA_AUDIO,
+            OPSTR_READ_MEDIA_VIDEO,
+            OPSTR_WRITE_MEDIA_VIDEO,
+            OPSTR_READ_MEDIA_IMAGES,
+            OPSTR_WRITE_MEDIA_IMAGES,
+            OPSTR_LEGACY_STORAGE,
+            OPSTR_ACCESS_MEDIA_LOCATION,
+            OPSTR_ACCESS_ACCESSIBILITY,
+            OPSTR_READ_DEVICE_IDENTIFIERS,
+            OPSTR_QUERY_ALL_PACKAGES,
+            OPSTR_MANAGE_EXTERNAL_STORAGE,
+            OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+            OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
+            OPSTR_INTERACT_ACROSS_PROFILES,
+            OPSTR_ACTIVATE_PLATFORM_VPN,
+            OPSTR_LOADER_USAGE_STATS,
+            OPSTR_MANAGE_ONGOING_CALLS,
+            OPSTR_NO_ISOLATED_STORAGE,
+            OPSTR_PHONE_CALL_MICROPHONE,
+            OPSTR_PHONE_CALL_CAMERA,
+            OPSTR_RECORD_AUDIO_HOTWORD,
+            OPSTR_MANAGE_CREDENTIALS,
+            OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+            OPSTR_RECORD_AUDIO_OUTPUT,
+            OPSTR_SCHEDULE_EXACT_ALARM,
+            OPSTR_FINE_LOCATION_SOURCE,
+            OPSTR_COARSE_LOCATION_SOURCE,
+            OPSTR_MANAGE_MEDIA,
+            OPSTR_UWB_RANGING,
+            OPSTR_NEARBY_WIFI_DEVICES,
+            OPSTR_ACTIVITY_RECOGNITION_SOURCE,
+            OPSTR_RECORD_INCOMING_PHONE_AUDIO,
+            OPSTR_ESTABLISH_VPN_SERVICE,
+            OPSTR_ESTABLISH_VPN_MANAGER,
+            OPSTR_ACCESS_RESTRICTED_SETTINGS,
+            OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+            OPSTR_READ_MEDIA_VISUAL_USER_SELECTED,
+            OPSTR_READ_WRITE_HEALTH_DATA,
+            OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+            OPSTR_RUN_USER_INITIATED_JOBS,
+            OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION,
+            OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
+            OPSTR_FOREGROUND_SERVICE_SPECIAL_USE,
+            OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS,
+            OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
+            OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+            OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+            OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
+            OPSTR_USE_FULL_SCREEN_INTENT,
+    })
+    public @interface AppOpString {}
+
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
     /** Access to fine location information. */
@@ -7736,18 +7880,19 @@
     }
 
     /**
-     * Start watching for noted app ops. An app op may be immediate or long running.
-     * Immediate ops are noted while long running ones are started and stopped. This
-     * method allows registering a listener to be notified when an app op is noted. If
-     * an op is being noted by any package you will get a callback. To change the
-     * watched ops for a registered callback you need to unregister and register it again.
+     * Start watching for noted app ops.
      *
-     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
-     * you can watch changes only for your UID.
+     * <p> Similar to {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}, but
+     * without an executor parameter.
      *
-     * @param ops The ops to watch.
-     * @param callback Where to report changes.
+     * <p> Note that the listener will be called on the main thread using
+     * {@link Context.getMainThread()}. To specify the execution thread, use
+     * {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}.
      *
+     * @param ops      the ops to watch
+     * @param listener listener to notify when an app op is noted
+     *
+     * @see #startWatchingNoted(String[], Executor, OnOpNotedListener)
      * @see #stopWatchingNoted(OnOpNotedListener)
      * @see #noteOp(String, int, String, String, String)
      *
@@ -7755,40 +7900,111 @@
      */
     @SystemApi
     @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
-    public void startWatchingNoted(@NonNull String[] ops, @NonNull OnOpNotedListener callback) {
+    public void startWatchingNoted(@NonNull @AppOpString String[] ops,
+     @NonNull OnOpNotedListener listener) {
         final int[] intOps = new int[ops.length];
         for (int i = 0; i < ops.length; i++) {
             intOps[i] = strOpToOp(ops[i]);
         }
-        startWatchingNoted(intOps, callback);
+        startWatchingNoted(intOps, listener);
     }
 
     /**
-     * Start watching for noted app ops. An app op may be immediate or long running.
-     * Immediate ops are noted while long running ones are started and stopped. This
-     * method allows registering a listener to be notified when an app op is noted. If
-     * an op is being noted by any package you will get a callback. To change the
-     * watched ops for a registered callback you need to unregister and register it again.
+     * Start watching for noted app ops.
      *
-     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
-     * you can watch changes only for your UID.
+     * <p> An app op may be immediate or long-running. Immediate ops are noted while long-running
+     * ones are started and stopped.
      *
-     * This allows observing noted ops by their raw op codes instead of string op names.
+     * <p> This method allows registering a listener to be notified when an app op is noted. To
+     * change the watched ops for a registered callback you need to unregister and register it
+     * again.
      *
-     * @param ops The ops to watch.
-     * @param callback Where to report changes.
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission you can
+     * watch changes only for your UID.
+     *
+     * @param ops      the ops to watch
+     * @param executor the executor on which the listener will be notified
+     * @param listener listener to notify when an app op is noted
+     *
+     * @see #startWatchingNoted(String[], OnOpNotedListener)
+     * @see #stopWatchingNoted(OnOpNotedListener)
+     * @see #noteOp(String, int, String, String, String)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingNoted(@NonNull @AppOpString String[] ops,
+     @CallbackExecutor @NonNull Executor executor, @NonNull OnOpNotedListener listener) {
+        final int[] intOps = new int[ops.length];
+        for (int i = 0; i < ops.length; i++) {
+            intOps[i] = strOpToOp(ops[i]);
+        }
+        startWatchingNoted(intOps, executor, listener);
+    }
+
+    /**
+     * Start watching for noted app ops.
+     *
+     * <p> Similar to {@link #startWatchingNoted(int[], Executor, OnOpNotedListener)}, but without
+     * an executor parameter.
+     *
+     * <p> This method is also similar to {@link #startWatchingNoted(String[], OnOpNotedListener)},
+     * but allows observing noted ops by their raw op codes instead of string op names.
+     *
+     * <p> Note that the listener will be called on the main thread using
+     * {@link Context.getMainThread()}. To specify the execution thread, use
+     * {@link {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}.
+     *
+     * @param ops      the ops to watch
+     * @param listener listener to notify when an app op is noted
      *
      * @see #startWatchingActive(int[], OnOpActiveChangedListener)
      * @see #startWatchingStarted(int[], OnOpStartedListener)
      * @see #startWatchingNoted(String[], OnOpNotedListener)
+     * @see #startWatchingNoted(int[], Executor, OnOpNotedListener)
+     *
+     * @hide
+     */
+    @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener listener) {
+        startWatchingNoted(ops, mContext.getMainExecutor(), listener);
+    }
+
+    /**
+     * Start watching for noted app ops.
+     *
+     * <p> This method is similar to
+     * {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}, but allows observing
+     * noted ops by their raw op codes instead of string op names.
+     *
+     * <p> An app op may be immediate or long-running. Immediate ops are noted while long-running
+     * ones are started and stopped.
+     *
+     * <p> This method allows registering a listener to be notified when an app op is noted. To
+     * change the watched ops for a registered callback you need to unregister and register it
+     * again.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission you
+     * can watch changes only for your UID.
+     *
+     * @param ops      the ops to watch
+     * @param executor the executor on which the listener will be notified
+     * @param listener listener to notify when an app op is noted
+     *
+     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+     * @see #startWatchingStarted(int[], OnOpStartedListener)
+     * @see #startWatchingNoted(int[], Executor, OnOpNotedListener)
+     * @see #startWatchingNoted(String[], OnOpNotedListener)
      *
      * @hide
      */
     @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
-    public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener callback) {
+    public void startWatchingNoted(@NonNull int[] ops,
+     @CallbackExecutor @NonNull Executor executor, @NonNull OnOpNotedListener listener) {
         IAppOpsNotedCallback cb;
         synchronized (mNotedWatchers) {
-            cb = mNotedWatchers.get(callback);
+            cb = mNotedWatchers.get(listener);
             if (cb != null) {
                 return;
             }
@@ -7796,13 +8012,21 @@
                 @Override
                 public void opNoted(int op, int uid, String packageName, String attributionTag,
                         int flags, int mode) {
-                    if (sAppOpInfos[op].name != null) {
-                        callback.onOpNoted(sAppOpInfos[op].name, uid, packageName, attributionTag,
-                                flags, mode);
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> {
+                            if (sAppOpInfos[op].name != null) {
+                                listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
+                                        attributionTag,
+                                        flags, mode);
+                            }
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
                     }
                 }
             };
-            mNotedWatchers.put(callback, cb);
+            mNotedWatchers.put(listener, cb);
         }
         try {
             mService.startWatchingNoted(ops, cb);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1c0be68..e3ec493 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -245,7 +245,7 @@
     @UnsupportedAppUsage
     private @NonNull Resources mResources;
     private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet.
-    private int mDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
+    private int mDeviceId = Context.DEVICE_ID_DEFAULT;
 
     /**
      * If set to {@code true} the resources for this context will be configured for mDisplay which
@@ -2812,7 +2812,7 @@
 
     @Override
     public @NonNull Context createDeviceContext(int deviceId) {
-        if (deviceId != VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+        if (deviceId != Context.DEVICE_ID_DEFAULT) {
             VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
             if (!vdm.isValidVirtualDeviceId(deviceId)) {
                 throw new IllegalArgumentException(
@@ -3092,7 +3092,7 @@
 
     @Override
     public void updateDeviceId(int updatedDeviceId) {
-        if (updatedDeviceId != VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+        if (updatedDeviceId != Context.DEVICE_ID_DEFAULT) {
             VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
             if (!vdm.isValidVirtualDeviceId(updatedDeviceId)) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ef5cd93..440ee20 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5758,7 +5758,7 @@
             List<Notification.Action> nonContextualActions = getNonContextualActions();
 
             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
-            boolean emphazisedMode = mN.fullScreenIntent != null
+            boolean emphasizedMode = mN.fullScreenIntent != null
                     || p.mCallStyleActions
                     || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
 
@@ -5771,7 +5771,7 @@
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
             }
-            big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
+            big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
             if (numActions > 0 && !p.mHideActions) {
                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
                 big.setViewVisibility(R.id.actions, View.VISIBLE);
@@ -5783,12 +5783,12 @@
                     boolean actionHasValidInput = hasValidRemoteInput(action);
                     validRemoteInput |= actionHasValidInput;
 
-                    final RemoteViews button = generateActionButton(action, emphazisedMode, p);
-                    if (actionHasValidInput && !emphazisedMode) {
+                    final RemoteViews button = generateActionButton(action, emphasizedMode, p);
+                    if (actionHasValidInput && !emphasizedMode) {
                         // Clear the drawable
                         button.setInt(R.id.action0, "setBackgroundResource", 0);
                     }
-                    if (emphazisedMode && i > 0) {
+                    if (emphasizedMode && i > 0) {
                         // Clear start margin from non-first buttons to reduce the gap between them.
                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
@@ -7475,10 +7475,10 @@
             Resources resources = context.getResources();
             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
             if (mPictureIcon != null) {
-                int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
+                int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_height_low_ram
                         : R.dimen.notification_big_picture_max_height);
-                int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
+                int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_width_low_ram
                         : R.dimen.notification_big_picture_max_width);
                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 50a7da1..5ee10a5 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -115,6 +115,20 @@
     String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
 
     /**
+     * A shell command that clears the network time signal used by {@link
+     * SystemClock#currentNetworkTimeClock()}.
+     * @hide
+     */
+    String SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME = "clear_system_clock_network_time";
+
+    /**
+     * A shell command that sets the network time signal used by {@link
+     * SystemClock#currentNetworkTimeClock()}.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME = "set_system_clock_network_time";
+
+    /**
      * A shared utility method to create a {@link ManualTimeSuggestion}.
      *
      * @hide
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index f3f43354..4a09186 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -38,9 +39,9 @@
      * @hide
      */
     public VirtualDevice(int id, @Nullable String name) {
-        if (id <= VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+        if (id <= Context.DEVICE_ID_DEFAULT) {
             throw new IllegalArgumentException("VirtualDevice ID mist be greater than "
-                    + VirtualDeviceManager.DEVICE_ID_DEFAULT);
+                    + Context.DEVICE_ID_DEFAULT);
         }
         mId = id;
         mName = name;
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index ae43c6e..4d2c7cb 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -98,16 +98,6 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
     /**
-     * The default device ID, which is the ID of the primary (non-virtual) device.
-     */
-    public static final int DEVICE_ID_DEFAULT = 0;
-
-    /**
-     * Invalid device ID.
-     */
-    public static final int DEVICE_ID_INVALID = -1;
-
-    /**
      * Broadcast Action: A Virtual Device was removed.
      *
      * <p class="note">This is a protected intent that can only be sent by the system.</p>
@@ -250,7 +240,7 @@
     public int getDeviceIdForDisplayId(int displayId) {
         if (mService == null) {
             Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
-            return DEVICE_ID_DEFAULT;
+            return Context.DEVICE_ID_DEFAULT;
         }
         try {
             return mService.getDeviceIdForDisplayId(displayId);
@@ -261,7 +251,7 @@
 
     /**
      * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
-     * {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
+     * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
      * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
      * created by {@link VirtualDeviceManager#createVirtualDevice(int, VirtualDeviceParams)}.
      *
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4dccc8d..bebb899 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -135,6 +135,15 @@
     @VisibleForTesting
     public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L;
 
+    /**
+     * The default device ID, which is the ID of the primary (non-virtual) device.
+     */
+    public static final int DEVICE_ID_DEFAULT = 0;
+    /**
+     * Invalid device ID.
+     */
+    public static final int DEVICE_ID_INVALID = -1;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "MODE_" }, value = {
             MODE_PRIVATE,
@@ -7175,7 +7184,7 @@
      * <p>
      * Applications that run on virtual devices may use this method to access the default device
      * capabilities and functionality (by passing
-     * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}. Similarly,
+     * {@link Context#DEVICE_ID_DEFAULT}. Similarly,
      * applications running on the default device may access the functionality of virtual devices.
      * </p>
      * <p>
@@ -7542,7 +7551,7 @@
      * determine whether they are running on a virtual device and identify that device.
      *
      * The device ID of the host device is
-     * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}
+     * {@link Context#DEVICE_ID_DEFAULT}
      *
      * <p>
      * If the underlying device ID is changed by the system, for example, when an
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5818ed7..85daf15 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3234,8 +3234,9 @@
     /**
      * Broadcast Action: The receiver's effective locale has changed.
      *
-     * This happens when the device locale, or the receiving app's locale
-     * (set via {@link android.app.LocaleManager#setApplicationLocales}) changed.
+     * This happens when the device locale, the receiving app's locale
+     * (set via {@link android.app.LocaleManager#setApplicationLocales}) or language tags
+     * of Regional preferences changed.
      *
      * Can be received by manifest-declared receivers.
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a0c620a..d927c5e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5157,6 +5157,8 @@
      * Retrieve overall information about an application package that is
      * installed on the system.
      *
+     * Use {@link #getPackageInfo(String, PackageInfoFlags)} when long flags are needed.
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of the
      *            desired package.
      * @param flags Additional option flags to modify the data returned.
@@ -5169,9 +5171,7 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if no such package is available to the
      *             caller.
-     * @deprecated Use {@link #getPackageInfo(String, PackageInfoFlags)} instead.
      */
-    @Deprecated
     public abstract PackageInfo getPackageInfo(@NonNull String packageName, int flags)
             throws NameNotFoundException;
 
@@ -5195,6 +5195,8 @@
      * {@link #VERSION_CODE_HIGHEST} in the {@link VersionedPackage}
      * constructor.
      *
+     * Use {@link #getPackageInfo(VersionedPackage, PackageInfoFlags)} when long flags are needed.
+     *
      * @param versionedPackage The versioned package for which to query.
      * @param flags Additional option flags to modify the data returned.
      * @return A PackageInfo object containing information about the package. If
@@ -5206,9 +5208,7 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if no such package is available to the
      *             caller.
-     * @deprecated Use {@link #getPackageInfo(VersionedPackage, PackageInfoFlags)} instead.
      */
-    @Deprecated
     public abstract PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage,
             int flags) throws NameNotFoundException;
 
@@ -5226,6 +5226,8 @@
      * Retrieve overall information about an application package that is
      * installed on the system.
      *
+     * Use {@link #getPackageInfoAsUser(String, PackageInfoFlags, int)} when long flags are needed.
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of the
      *            desired package.
      * @param flags Additional option flags to modify the data returned.
@@ -5239,10 +5241,8 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if no such package is available to the
      *             caller.
-     * @deprecated Use {@link #getPackageInfoAsUser(String, PackageInfoFlags, int)} instead.
      * @hide
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @UnsupportedAppUsage
@@ -5376,15 +5376,16 @@
      * Note that the same package may have different GIDs under different
      * {@link UserHandle} on the same device.
      *
+     * Use {@link #getPackageGids(String, PackageInfoFlags)} when long flags are needed.
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of the
      *            desired package.
      * @return Returns an int array of the assigned gids, or null if there are
      *         none.
      * @throws NameNotFoundException if no such package is available to the
      *             caller.
-     * @deprecated Use {@link #getPackageGids(String, PackageInfoFlags)} instead.
      */
-    @Deprecated
+
     public abstract int[] getPackageGids(@NonNull String packageName, int flags)
             throws NameNotFoundException;
 
@@ -5404,14 +5405,14 @@
      * Note that the same package will have different UIDs under different
      * {@link UserHandle} on the same device.
      *
+     * Use {@link #getPackageUid(String, PackageInfoFlags)} when long flags are needed.
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of the
      *            desired package.
      * @return Returns an integer UID who owns the given package name.
      * @throws NameNotFoundException if no such package is available to the
      *             caller.
-     * @deprecated Use {@link #getPackageUid(String, PackageInfoFlags)} instead.
      */
-    @Deprecated
     public abstract int getPackageUid(@NonNull String packageName, int flags)
             throws NameNotFoundException;
 
@@ -5445,10 +5446,9 @@
 
     /**
      * See {@link #getPackageUidAsUser(String, PackageInfoFlags, int)}.
-     * @deprecated Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} instead.
+     * Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} when long flags are needed.
      * @hide
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @UnsupportedAppUsage
     public abstract int getPackageUidAsUser(@NonNull String packageName,
@@ -5589,6 +5589,8 @@
      * Retrieve all of the information we know about a particular
      * package/application.
      *
+     * Use {@link #getApplicationInfo(String, ApplicationInfoFlags)} when long flags are needed.
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of an
      *            application.
      * @param flags Additional option flags to modify the data returned.
@@ -5601,10 +5603,8 @@
      *         which had been deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
-     * @deprecated Use {@link #getApplicationInfo(String, ApplicationInfoFlags)} instead.
      */
     @NonNull
-    @Deprecated
     public abstract ApplicationInfo getApplicationInfo(@NonNull String packageName,
             int flags) throws NameNotFoundException;
 
@@ -5619,13 +5619,13 @@
     }
 
     /**
-     * @deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, int)} instead.
+     * Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, int)} when long flags are
+     * needed.
      * {@hide}
      */
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
-    @Deprecated
     public abstract ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
             int flags, @UserIdInt int userId) throws NameNotFoundException;
 
@@ -5642,6 +5642,9 @@
      * Retrieve all of the information we know about a particular
      * package/application, for a specific user.
      *
+     * Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, UserHandle)} when long
+     * flags are needed.
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of an
      *            application.
      * @param flags Additional option flags to modify the data returned.
@@ -5654,14 +5657,11 @@
      *         which had been deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
-     * @deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, UserHandle)}
-     * instead.
      * @hide
      */
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
-    @Deprecated
     public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
             int flags, @NonNull UserHandle user)
             throws NameNotFoundException {
@@ -5694,6 +5694,8 @@
      * Retrieve all of the information we know about a particular activity
      * class.
      *
+     * Use {@link #getActivityInfo(ComponentName, ComponentInfoFlags)} when long flags are needed.
+     *
      * @param component The full component name (i.e.
      *            com.google.apps.contacts/com.google.apps.contacts.
      *            ContactsList) of an Activity class.
@@ -5702,9 +5704,7 @@
      *         activity.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
-     * @deprecated Use {@link #getActivityInfo(ComponentName, ComponentInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component,
             int flags) throws NameNotFoundException;
@@ -5734,6 +5734,8 @@
      * Retrieve all of the information we know about a particular receiver
      * class.
      *
+     * Use {@link #getReceiverInfo(ComponentName, ComponentInfoFlags)} when long flags are needed.
+     *
      * @param component The full component name (i.e.
      *            com.google.apps.calendar/com.google.apps.calendar.
      *            CalendarAlarm) of a Receiver class.
@@ -5742,9 +5744,7 @@
      *         receiver.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
-     * @deprecated Use {@link #getReceiverInfo(ComponentName, ComponentInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract ActivityInfo getReceiverInfo(@NonNull ComponentName component,
             int flags) throws NameNotFoundException;
@@ -5762,6 +5762,8 @@
     /**
      * Retrieve all of the information we know about a particular service class.
      *
+     * Use {@link #getServiceInfo(ComponentName, ComponentInfoFlags)} when long flags are needed.
+     *
      * @param component The full component name (i.e.
      *            com.google.apps.media/com.google.apps.media.
      *            BackgroundPlayback) of a Service class.
@@ -5769,9 +5771,7 @@
      * @return A {@link ServiceInfo} object containing information about the
      *         service.
      * @throws NameNotFoundException if the component cannot be found on the system.
-     * @deprecated Use {@link #getServiceInfo(ComponentName, ComponentInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract ServiceInfo getServiceInfo(@NonNull ComponentName component,
             int flags) throws NameNotFoundException;
@@ -5790,6 +5790,8 @@
      * Retrieve all of the information we know about a particular content
      * provider class.
      *
+     * Use {@link #getProviderInfo(ComponentName, ComponentInfoFlags)} when long flags are needed.
+     *
      * @param component The full component name (i.e.
      *            com.google.providers.media/com.google.providers.media.
      *            MediaProvider) of a ContentProvider class.
@@ -5798,9 +5800,7 @@
      *         provider.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
-     * @deprecated Use {@link #getProviderInfo(ComponentName, ComponentInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract ProviderInfo getProviderInfo(@NonNull ComponentName component,
             int flags) throws NameNotFoundException;
@@ -5849,6 +5849,8 @@
     /**
      * Return a List of all packages that are installed for the current user.
      *
+     * Use {@link #getInstalledPackages(PackageInfoFlags)} when long flags are needed.
+     *
      * @param flags Additional option flags to modify the data returned.
      * @return A List of PackageInfo objects, one for each installed package,
      *         containing information about the package. In the unlikely case
@@ -5858,15 +5860,12 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
-     * @deprecated Use {@link #getInstalledPackages(PackageInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<PackageInfo> getInstalledPackages(int flags);
 
     /**
      * See {@link #getInstalledPackages(int)}.
-     * @param flags
      */
     @NonNull
     public List<PackageInfo> getInstalledPackages(@NonNull PackageInfoFlags flags) {
@@ -5896,6 +5895,9 @@
      * Return a List of all installed packages that are currently holding any of
      * the given permissions.
      *
+     * Use {@link #getPackagesHoldingPermissions(String[], PackageInfoFlags)} when long flags are
+     * needed.
+     *
      * @param flags Additional option flags to modify the data returned.
      * @return A List of PackageInfo objects, one for each installed package
      *         that holds any of the permissions that were provided, containing
@@ -5906,9 +5908,7 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
-     * @deprecated Use {@link #getPackagesHoldingPermissions(String[], PackageInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<PackageInfo> getPackagesHoldingPermissions(
             @NonNull String[] permissions, int flags);
@@ -5927,6 +5927,8 @@
      * Return a List of all packages that are installed on the device, for a
      * specific user.
      *
+     * Use {@link #getInstalledPackagesAsUser(PackageInfoFlags, int)} when long flags are needed.
+     *
      * @param flags Additional option flags to modify the data returned.
      * @param userId The user for whom the installed packages are to be listed
      * @return A List of PackageInfo objects, one for each installed package,
@@ -5937,10 +5939,8 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
-     * @deprecated Use {@link #getInstalledPackagesAsUser(PackageInfoFlags, int)} instead.
      * @hide
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @SystemApi
@@ -6652,6 +6652,8 @@
      * applications including those deleted with {@code DELETE_KEEP_DATA}
      * (partially installed apps with data directory) will be returned.
      *
+     * Use {@link #getInstalledApplications(ApplicationInfoFlags)} when long flags are needed.
+     *
      * @param flags Additional option flags to modify the data returned.
      * @return A List of ApplicationInfo objects, one for each installed
      *         application. In the unlikely case there are no installed
@@ -6661,10 +6663,8 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
-     * @deprecated  Use {@link #getInstalledApplications(ApplicationInfoFlags)} instead.
      */
     @NonNull
-    @Deprecated
     public abstract List<ApplicationInfo> getInstalledApplications(int flags);
 
     /**
@@ -6683,6 +6683,9 @@
      * {@code DELETE_KEEP_DATA} (partially installed apps with data directory)
      * will be returned.
      *
+     * Use {@link #getInstalledApplicationsAsUser(ApplicationInfoFlags, int)} when long flags are
+     * needed.
+     *
      * @param flags Additional option flags to modify the data returned.
      * @param userId The user for whom the installed applications are to be
      *            listed
@@ -6694,13 +6697,11 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
-     * @deprecated  Use {@link #getInstalledApplicationsAsUser(ApplicationInfoFlags, int)} instead.
      * @hide
      */
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @TestApi
-    @Deprecated
     public abstract List<ApplicationInfo> getInstalledApplicationsAsUser(
             int flags, @UserIdInt int userId);
 
@@ -6859,13 +6860,13 @@
     /**
      * Get a list of shared libraries on the device.
      *
+     * Use {@link #getSharedLibraries(PackageInfoFlags)} when long flags are needed.
+     *
      * @param flags To filter the libraries to return.
      * @return The shared library list.
      *
      * @see #MATCH_UNINSTALLED_PACKAGES
-     * @deprecated Use {@link #getSharedLibraries(PackageInfoFlags)} instead.
      */
-    @Deprecated
     public abstract @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags);
 
     /**
@@ -6880,6 +6881,8 @@
     /**
      * Get a list of shared libraries on the device.
      *
+     * Use {@link #getSharedLibrariesAsUser(PackageInfoFlags, int)} when long flags are needed.
+     *
      * @param flags To filter the libraries to return.
      * @param userId The user to query for.
      * @return The shared library list.
@@ -6890,9 +6893,7 @@
      * @see #MATCH_UNINSTALLED_PACKAGES
      *
      * @hide
-     * @deprecated Use {@link #getSharedLibrariesAsUser(PackageInfoFlags, int)} instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags,
             @UserIdInt int userId);
@@ -6910,14 +6911,14 @@
     /**
      * Get the list of shared libraries declared by a package.
      *
+     * Use {@link #getDeclaredSharedLibraries(String, PackageInfoFlags)} when long flags are needed.
+     *
      * @param packageName the package name to query
      * @param flags the flags to filter packages
      * @return the shared library list
      *
      * @hide
-     * @deprecated Use {@link #getDeclaredSharedLibraries(String, PackageInfoFlags)} instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @RequiresPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES)
@@ -7026,6 +7027,8 @@
      * Intent.resolveActivity(PackageManager)} do.
      * </p>
      *
+     * Use {@link #resolveActivity(Intent, ResolveInfoFlags)} when long flags are needed.
+     *
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned. The
@@ -7037,9 +7040,7 @@
      *         matching activity was found. If multiple matching activities are
      *         found and there is no default set, returns a ResolveInfo object
      *         containing something else, such as the activity resolver.
-     * @deprecated Use {@link #resolveActivity(Intent, ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @Nullable
     public abstract ResolveInfo resolveActivity(@NonNull Intent intent, int flags);
 
@@ -7066,6 +7067,8 @@
      * Intent.resolveActivity(PackageManager)} do.
      * </p>
      *
+     * Use {@link #resolveActivityAsUser(Intent, ResolveInfoFlags, int)} when long flags are needed.
+     *
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned. The
@@ -7079,9 +7082,7 @@
      *         found and there is no default set, returns a ResolveInfo object
      *         containing something else, such as the activity resolver.
      * @hide
-     * @deprecated Use {@link #resolveActivityAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @Nullable
     @UnsupportedAppUsage
@@ -7102,6 +7103,8 @@
     /**
      * Retrieve all activities that can be performed for the given intent.
      *
+     * Use {@link #queryIntentActivities(Intent, ResolveInfoFlags)} when long flags are needed.
+     *
      * @param intent The desired intent as per resolveActivity().
      * @param flags Additional option flags to modify the data returned. The
      *            most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
@@ -7113,9 +7116,7 @@
      *         words, the first item is what would be returned by
      *         {@link #resolveActivity}. If there are no matching activities, an
      *         empty list is returned.
-     * @deprecated Use {@link #queryIntentActivities(Intent, ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags);
 
@@ -7133,6 +7134,9 @@
      * Retrieve all activities that can be performed for the given intent, for a
      * specific user.
      *
+     * Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, int)} when long flags are
+     * needed.
+     *
      * @param intent The desired intent as per resolveActivity().
      * @param flags Additional option flags to modify the data returned. The
      *            most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
@@ -7145,9 +7149,7 @@
      *         {@link #resolveActivity}. If there are no matching activities, an
      *         empty list is returned.
      * @hide
-     * @deprecated Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
@@ -7169,6 +7171,9 @@
      * Retrieve all activities that can be performed for the given intent, for a
      * specific user.
      *
+     * Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, UserHandle)} when long
+     * flags are needed.
+     *
      * @param intent The desired intent as per resolveActivity().
      * @param flags Additional option flags to modify the data returned. The
      *            most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
@@ -7182,10 +7187,7 @@
      *         {@link #resolveActivity}. If there are no matching activities, an
      *         empty list is returned.
      * @hide
-     * @deprecated Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, UserHandle)}
-     * instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
@@ -7215,6 +7217,9 @@
      * final ResolveInfo list in a reasonable order, with no duplicates, based
      * on those inputs.
      *
+     * Use {@link #queryIntentActivityOptions(ComponentName, List, Intent, ResolveInfoFlags)} when
+     * long flags are needed.
+     *
      * @param caller The class name of the activity that is making the request.
      *            This activity will never appear in the output list. Can be
      *            null.
@@ -7231,10 +7236,7 @@
      *         activities that can handle <var>intent</var> but did not get
      *         included by one of the <var>specifics</var> intents. If there are
      *         no matching activities, an empty list is returned.
-     * @deprecated Use {@link #queryIntentActivityOptions(ComponentName, List, Intent,
-     * ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
             @Nullable Intent[] specifics, @NonNull Intent intent, int flags);
@@ -7253,14 +7255,14 @@
     /**
      * Retrieve all receivers that can handle a broadcast of the given intent.
      *
+     * Use {@link #queryBroadcastReceivers(Intent, ResolveInfoFlags)} when long flags are needed.
+     *
      * @param intent The desired intent as per resolveActivity().
      * @param flags Additional option flags to modify the data returned.
      * @return Returns a List of ResolveInfo objects containing one entry for
      *         each matching receiver, ordered from best to worst. If there are
      *         no matching receivers, an empty list or null is returned.
-     * @deprecated Use {@link #queryBroadcastReceivers(Intent, ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, int flags);
 
@@ -7278,6 +7280,9 @@
      * Retrieve all receivers that can handle a broadcast of the given intent,
      * for a specific user.
      *
+     * Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, UserHandle)} when long
+     * flags are needed.
+     *
      * @param intent The desired intent as per resolveActivity().
      * @param flags Additional option flags to modify the data returned.
      * @param userHandle UserHandle of the user being queried.
@@ -7285,10 +7290,7 @@
      *         each matching receiver, ordered from best to worst. If there are
      *         no matching receivers, an empty list or null is returned.
      * @hide
-     * @deprecated Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, UserHandle)}
-     * instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @SystemApi
@@ -7312,10 +7314,9 @@
 
     /**
      * @hide
-     * @deprecated Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, int)}
-     * instead.
+     * Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, int)} when long flags are
+     * needed.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
@@ -7353,15 +7354,15 @@
     /**
      * Determine the best service to handle for a given Intent.
      *
+     * Use {@link #resolveService(Intent, ResolveInfoFlags)} when long flags are needed.
+     *
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned.
      * @return Returns a ResolveInfo object containing the final service intent
      *         that was determined to be the best action. Returns null if no
      *         matching service was found.
-     * @deprecated Use {@link #resolveService(Intent, ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @Nullable
     public abstract ResolveInfo resolveService(@NonNull Intent intent, int flags);
 
@@ -7376,9 +7377,8 @@
 
     /**
      * @hide
-     * @deprecated Use {@link #resolveServiceAsUser(Intent, ResolveInfoFlags, int)} instead.
+     * Use {@link #resolveServiceAsUser(Intent, ResolveInfoFlags, int)} when long flags are needed.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @Nullable
     public abstract ResolveInfo resolveServiceAsUser(@NonNull Intent intent,
@@ -7398,6 +7398,8 @@
     /**
      * Retrieve all services that can match the given intent.
      *
+     * Use {@link #queryIntentServices(Intent, ResolveInfoFlags)} when long flags are needed.
+     *
      * @param intent The desired intent as per resolveService().
      * @param flags Additional option flags to modify the data returned.
      * @return Returns a List of ResolveInfo objects containing one entry for
@@ -7405,9 +7407,7 @@
      *         words, the first item is what would be returned by
      *         {@link #resolveService}. If there are no matching services, an
      *         empty list or null is returned.
-     * @deprecated Use {@link #queryIntentServices(Intent, ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentServices(@NonNull Intent intent,
             int flags);
@@ -7425,6 +7425,9 @@
     /**
      * Retrieve all services that can match the given intent for a given user.
      *
+     * Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, int)} when long flags are
+     * needed.
+     *
      * @param intent The desired intent as per resolveService().
      * @param flags Additional option flags to modify the data returned.
      * @param userId The user id.
@@ -7434,9 +7437,7 @@
      *         {@link #resolveService}. If there are no matching services, an
      *         empty list or null is returned.
      * @hide
-     * @deprecated Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
@@ -7457,6 +7458,9 @@
     /**
      * Retrieve all services that can match the given intent for a given user.
      *
+     * Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, UserHandle)} when long flags
+     * are needed.
+     *
      * @param intent The desired intent as per resolveService().
      * @param flags Additional option flags to modify the data returned.
      * @param user The user being queried.
@@ -7466,10 +7470,7 @@
      *         {@link #resolveService}. If there are no matching services, an
      *         empty list or null is returned.
      * @hide
-     * @deprecated Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, UserHandle)}
-     * instead.
      */
-    @Deprecated
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
@@ -7492,6 +7493,9 @@
     /**
      * Retrieve all providers that can match the given intent.
      *
+     * Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, int)} when long flags
+     * are needed.
+     *
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned.
@@ -7500,10 +7504,7 @@
      *         each matching provider, ordered from best to worst. If there are
      *         no matching services, an empty list or null is returned.
      * @hide
-     * @deprecated Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, int)}
-     * instead.
      */
-    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
@@ -7524,6 +7525,9 @@
     /**
      * Retrieve all providers that can match the given intent.
      *
+     * Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, UserHandle)} when
+     * long flags are needed.
+     *
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned.
@@ -7532,10 +7536,7 @@
      *         each matching provider, ordered from best to worst. If there are
      *         no matching services, an empty list or null is returned.
      * @hide
-     * @deprecated Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags,
-     * UserHandle)} instead.
      */
-    @Deprecated
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
@@ -7559,15 +7560,16 @@
     /**
      * Retrieve all providers that can match the given intent.
      *
+     * Use {@link #queryIntentContentProviders(Intent, ResolveInfoFlags)} when long flags are
+     * needed.
+     *
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned.
      * @return Returns a List of ResolveInfo objects containing one entry for
      *         each matching provider, ordered from best to worst. If there are
      *         no matching services, an empty list or null is returned.
-     * @deprecated Use {@link #queryIntentContentProviders(Intent, ResolveInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent,
             int flags);
@@ -7591,13 +7593,13 @@
      * ProviderInfo info = packageManager.resolveContentProvider(uri.getAuthority(), flags);
      * </pre>
      *
+     * Use {@link #resolveContentProvider(String, ComponentInfoFlags)} when long flags are needed.
+     *
      * @param authority The authority of the provider to find.
      * @param flags Additional option flags to modify the data returned.
      * @return A {@link ProviderInfo} object containing information about the
      *         provider. If a provider was not found, returns null.
-     * @deprecated Use {@link #resolveContentProvider(String, ComponentInfoFlags)} instead.
      */
-    @Deprecated
     @Nullable
     public abstract ProviderInfo resolveContentProvider(@NonNull String authority,
             int flags);
@@ -7615,14 +7617,15 @@
     /**
      * Find a single content provider by its base path name.
      *
+     * Use {@link #resolveContentProviderAsUser(String, ComponentInfoFlags, int)} when long flags
+     * are needed.
+     *
      * @param providerName The name of the provider to find.
      * @param flags Additional option flags to modify the data returned.
      * @param userId The user id.
      * @return A {@link ProviderInfo} object containing information about the
      *         provider. If a provider was not found, returns null.
      * @hide
-     * @deprecated Use {@link #resolveContentProviderAsUser(String, ComponentInfoFlags, int)}
-     * instead.
      */
     @SuppressWarnings("HiddenAbstractMethod")
     @Nullable
@@ -7647,6 +7650,9 @@
      * <em>Note: unlike most other methods, an empty result set is indicated
      * by a null return instead of an empty list.</em>
      *
+     * Use {@link #queryContentProviders(String, int, ComponentInfoFlags)} when long flags are
+     * needed.
+     *
      * @param processName If non-null, limits the returned providers to only
      *            those that are hosted by the given process. If null, all
      *            content providers are returned.
@@ -7657,9 +7663,7 @@
      *         each provider either matching <var>processName</var> or, if
      *         <var>processName</var> is null, all known content providers.
      *         <em>If there are no matching providers, null is returned.</em>
-     * @deprecated Use {@link #queryContentProviders(String, int, ComponentInfoFlags)} instead.
      */
-    @Deprecated
     @NonNull
     public abstract List<ProviderInfo> queryContentProviders(
             @Nullable String processName, int uid, int flags);
@@ -7687,11 +7691,11 @@
      * to mark GAL providers, rather than intent filters, so we can't use
      * {@link #queryIntentContentProviders} for that.
      *
+     * Use {@link #queryContentProviders(String, int, ComponentInfoFlags, String)} when long flags
+     * are needed.
+     *
      * @hide
-     * @deprecated Use {@link #queryContentProviders(String, int, ComponentInfoFlags, String)}
-     * instead.
      */
-    @Deprecated
     @NonNull
     public List<ProviderInfo> queryContentProviders(@Nullable String processName,
             int uid, int flags, String metaDataKey) {
@@ -8237,13 +8241,13 @@
      * Retrieve overall information about an application package defined in a
      * package archive file
      *
+     * Use {@link #getPackageArchiveInfo(String, PackageInfoFlags)} when long flags are needed.
+     *
      * @param archiveFilePath The path to the archive file
      * @param flags Additional option flags to modify the data returned.
      * @return A PackageInfo object containing information about the package
      *         archive. If the package could not be parsed, returns null.
-     * @deprecated Use {@link #getPackageArchiveInfo(String, PackageInfoFlags)} instead.
      */
-    @Deprecated
     @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) {
         return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags));
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 02b9308..1a3c3d9 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -376,8 +376,8 @@
      *   <li>Headless system apps</li>
      *   <li><a href="{@docRoot}guide/topics/admin/device-admin">Device admin apps</a></li>
      *   <li>Active VPN apps</li>
-     *   <li>Apps holding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
-     *       {@link Manifest.permission#USE_EXACT_ALARM} permission.</li>
+     *   <li>Apps holding {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} or
+     *       {@link android.Manifest.permission#USE_EXACT_ALARM} permission.</li>
      * </ul>
      * </p>
      */
@@ -393,7 +393,7 @@
      *
      * <p>Unlike other foreground service types, this type is not associated with a specific use
      * case, and it will not require any special permissions
-     * (besides {@link Manifest.permission#FOREGROUND_SERVICE}).
+     * (besides {@link android.Manifest.permission#FOREGROUND_SERVICE}).
      *
      * However, this type has the following restrictions.
      *
@@ -401,19 +401,21 @@
      *     <li>
      *         The type has a 3 minute timeout.
      *         A foreground service of this type must be stopped within the timeout by
-     *         {@link android.app.Service#stopSelf),
-     *         or {@link android.content.Context#stopService).
-     *         {@link android.app.Service#stopForeground) will also work, which will demote the
+     *         {@link android.app.Service#stopSelf()},
+     *         {@link android.content.Context#stopService(android.content.Intent)}
+     *         or their overloads).
+     *         {@link android.app.Service#stopForeground(int)} will also work,
+     *         which will demote the
      *         service to a "background" service, which will soon be stopped by the system.
      *
-     *         <p>The system will <em>not</em> automatically stop it.
-     *
      *         <p>If the service isn't stopped within the timeout,
-     *         {@link android.app.Service#onTimeout(int)} will be called.
+     *         {@link android.app.Service#onTimeout(int)} will be called. Note, even when the
+     *         system calls this callback, it will not stop the service automatically.
+     *         You still need to stop the service using one of the aforementioned
+     *         ways even when you get this callback.
      *
      *         <p>If the service is still not stopped after the callback,
-     *         the app will be declared an ANR after a short grace period of several seconds.
-     *
+     *         the app will be declared an ANR, after a short grace period of several seconds.
      *     <li>
      *         A foreground service of this type cannot be made "sticky"
      *         (see {@link android.app.Service#START_STICKY}). That is, if an app is killed
@@ -427,10 +429,26 @@
      *         <a href="/guide/components/foreground-services#background-start-restrictions>
      *             Restrictions on background starts
      *         </a>
+     *     <li>
+     *         You can combine multiple foreground services types with {@code |}s, and you can
+     *         combine
+     *         {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+     *         with other types as well.
+     *         However,
+     *         {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+     *         is for situations
+     *         where you have no other valid foreground services to use and the timeout is long
+     *         enough for the task, and if you can use other types, there's no point using
+     *         this type.
+     *         For this reason, if
+     *         {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+     *         is combined with other foreground service types, the system will simply ignore
+     *         it, and as a result,
+     *         none of the above restrictions will apply (e.g. there'll be no timeout).
      * </ul>
      *
-     * <p>Note, even though
-     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+     * <p>Also note, even though
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
      * was added
      * on Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
      * it can be also used on
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 67634dc..3c10e81 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -78,7 +78,7 @@
                         Resources.getSystem()
                                 .getString(
                                         com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
+                                                .config_credentialManagerReceiverComponent));
         intent.setComponent(componentName);
         intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
         return intent;
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 9388ae3..1a3e0b0 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -17,10 +17,10 @@
 package android.hardware;
 
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.companion.virtual.VirtualDeviceManager;
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 19719a8..fbc0184 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -142,24 +142,18 @@
                     PackageManager.PERMISSION_GRANTED;
         }
 
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
         mFoldStateListener = new FoldStateListener(context);
         try {
-            context.getSystemService(DeviceStateManager.class)
-                    .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+            context.getSystemService(DeviceStateManager.class).registerCallback(
+                    new HandlerExecutor(CameraManagerGlobal.get().getDeviceStateHandler()),
+                    mFoldStateListener);
         } catch (IllegalStateException e) {
             Log.v(TAG, "Failed to register device state listener!");
             Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
-            mHandlerThread.quitSafely();
-            mHandler = null;
             mFoldStateListener = null;
         }
     }
 
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
     private FoldStateListener mFoldStateListener;
 
     /**
@@ -1645,6 +1639,9 @@
         private ICameraService mCameraService;
         private boolean mHasOpenCloseListenerPermission = false;
 
+        private HandlerThread mDeviceStateHandlerThread;
+        private Handler mDeviceStateHandler;
+
         // Singleton, don't allow construction
         private CameraManagerGlobal() { }
 
@@ -1658,6 +1655,18 @@
             return gCameraManager;
         }
 
+        public Handler getDeviceStateHandler() {
+            synchronized(mLock) {
+                if (mDeviceStateHandlerThread == null) {
+                    mDeviceStateHandlerThread = new HandlerThread(TAG);
+                    mDeviceStateHandlerThread.start();
+                    mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper());
+                }
+
+                return mDeviceStateHandler;
+            }
+        }
+
         @Override
         public IBinder asBinder() {
             return this;
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 4009fa7..1c6de04 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -52,7 +52,7 @@
     }
 
     /** The sensor id for this operation. */
-    private final int mSensorId;
+    private int mSensorId;
     private static int defaultSensorId() {
         return -1;
     }
@@ -299,6 +299,15 @@
     }
 
     /**
+     * The sensor id for this operation.
+     */
+    @DataClass.Generated.Member
+    public @NonNull FaceAuthenticateOptions setSensorId( int value) {
+        mSensorId = value;
+        return this;
+    }
+
+    /**
      * The package name for that operation that should be used for
      * {@link android.app.AppOpsManager} verification.
      *
@@ -610,10 +619,10 @@
     }
 
     @DataClass.Generated(
-            time = 1676508211385L,
+            time = 1677119626034L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/face/FaceAuthenticateOptions.java",
-            inputSignatures = "private final  int mUserId\nprivate final  int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final  int AUTHENTICATE_REASON_UNKNOWN\npublic static final  int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final  int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final  int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final  int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final  int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final  int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final  int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final  int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  int defaultDisplayState()\nprivate static  int defaultAuthenticateReason()\nprivate static  int defaultWakeReason()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final  int AUTHENTICATE_REASON_UNKNOWN\npublic static final  int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final  int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final  int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final  int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final  int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final  int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final  int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final  int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  int defaultDisplayState()\nprivate static  int defaultAuthenticateReason()\nprivate static  int defaultWakeReason()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 2857627..9d5073e 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -68,7 +68,7 @@
     // by BiometricService. To start authentication after the clients are ready, use
     // startPreparedClient().
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+    void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long operationId, IBiometricSensorReceiver sensorReceiver,
             in FaceAuthenticateOptions options, long requestId, int cookie,
             boolean allowBackgroundAuthentication);
diff --git a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
index cecb317..763246e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
+++ b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
@@ -46,7 +46,7 @@
     }
 
     /** The sensor id for this operation. */
-    private final int mSensorId;
+    private int mSensorId;
     private static int defaultSensorId() {
         return SENSOR_ID_ANY;
     }
@@ -176,6 +176,15 @@
     }
 
     /**
+     * The sensor id for this operation.
+     */
+    @DataClass.Generated.Member
+    public @NonNull FingerprintAuthenticateOptions setSensorId( int value) {
+        mSensorId = value;
+        return this;
+    }
+
+    /**
      * The package name for that operation that should be used for
      * {@link android.app.AppOpsManager} verification.
      *
@@ -433,10 +442,10 @@
     }
 
     @DataClass.Generated(
-            time = 1676508212083L,
+            time = 1677119626721L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java",
-            inputSignatures = "private final  int mUserId\nprivate final  int mSensorId\nprivate final  boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  boolean defaultIgnoreEnrollmentState()\nprivate static  int defaultDisplayState()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final  boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  boolean defaultIgnoreEnrollmentState()\nprivate static  int defaultDisplayState()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index e3ae299..ec5749e 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -73,8 +73,8 @@
     // by BiometricService. To start authentication after the clients are ready, use
     // startPreparedClient().
     @EnforcePermission("MANAGE_BIOMETRIC")
-    void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
-            IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+    void prepareForAuthentication(IBinder token, long operationId,
+            IBiometricSensorReceiver sensorReceiver, in FingerprintAuthenticateOptions options, long requestId,
             int cookie, boolean allowBackgroundAuthentication);
 
     // Starts authentication with the previously prepared client.
diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java
new file mode 100644
index 0000000..afb5ff7
--- /dev/null
+++ b/core/java/android/os/CancellationSignalBeamer.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.system.SystemCleaner;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.HashMap;
+
+/**
+ * A transport for {@link CancellationSignal}, but unlike
+ * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the
+ * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable
+ * request.
+ *
+ * <p><strong>Important:</strong> For this to work, the following invariants must be held up:
+ * <ul>
+ *     <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result
+ *     (otherwise, the token will be leaked and cancellation isn't propagated), and that call
+ *     must happen after the call using the
+ *     token is sent (otherwise, any concurrent cancellation may be lost). It is strongly
+ *     recommended to use try-with-resources on the token.
+ *     <li>The cancel(), forget() and cancellable operations transporting the token must either
+ *     all be oneway on the same binder, or all be non-oneway to guarantee proper ordering.
+ *     <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there
+ *     can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}.
+ *
+ * </ul>
+ * <p>Caveats:
+ * <ul>
+ *     <li>Cancellation is only ever dispatched after the token is closed, and thus after the
+ *     call performing the cancellable operation (if the invariants are followed). The operation
+ *     must therefore not block the incoming binder thread, or cancellation won't be possible.
+ *     <li>Consequently, in the unlikely event that the sender dies right after beaming an already
+ *     cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with
+ *     {@link CancellationSignal#createTransport()}).
+ *     <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources
+ *         / when closing the token. If the receiver is in the same process, and the signal is
+ *         already cancelled, this may invoke the target's OnCancelListener during that phase.
+ * </ul>
+ *
+ *
+ * <p>Usage:
+ * <pre>
+ *  // Sender:
+ *
+ *  class FooManager {
+ *    var mCancellationSignalSender = new CancellationSignalBeamer.Sender() {
+ *      &#064;Override
+ *      public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
+ *
+ *      &#064;Override
+ *      public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); }
+ *    };
+ *
+ *    public void doCancellableOperation(..., CancellationSignal cs) {
+ *      try (var csToken = mCancellationSignalSender.beam(cs)) {
+ *          remoteIFooService.doCancellableOperation(..., csToken);
+ *      }
+ *    }
+ *  }
+ *
+ *  // Receiver:
+ *
+ *  class FooManagerService extends IFooService.Stub {
+ *    var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver();
+ *
+ *    &#064;Override
+ *    public void doCancellableOperation(..., IBinder csToken) {
+ *      CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
+ *      // ...
+ *    }
+ *
+ *    &#064;Override
+ *    public void onCancelToken(..., IBinder csToken) {
+ *      mCancellationSignalReceiver.cancelToken(csToken))
+ *    }
+ *
+ *    &#064;Override
+ *    public void onForgetToken(..., IBinder csToken) {
+ *      mCancellationSignalReceiver.forgetToken(csToken))
+ *    }
+ *  }
+ *
+ * </pre>
+ *
+ * @hide
+ */
+public class CancellationSignalBeamer {
+
+    static final Cleaner sCleaner = SystemCleaner.cleaner();
+
+    /** The sending side of an {@link CancellationSignalBeamer} */
+    public abstract static class Sender {
+
+        /**
+         * Beams a {@link CancellationSignal} through an existing Binder interface.
+         *
+         * @param cs the {@code CancellationSignal} to beam, or {@code null}.
+         * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em>
+         *         the binder call transporting it to the remote process, best with
+         *         try-with-resources. {@code null} if {@code cs} was {@code null}.
+         */
+        // TODO(b/254888024): @MustBeClosed
+        @Nullable
+        public CloseableToken beam(@Nullable CancellationSignal cs) {
+            if (cs == null) {
+                return null;
+            }
+            return new Token(this, cs);
+        }
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} was closed.
+         *
+         * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See
+         * {@link CancellationSignalBeamer} for details.
+         */
+        public abstract void onCancel(IBinder token);
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} was GC'd.
+         *
+         * MUST be forwarded to {@link Receiver#forget} with proper ordering. See
+         * {@link CancellationSignalBeamer} for details.
+         */
+        public abstract void onForget(IBinder token);
+
+        private static class Token extends Binder implements CloseableToken, Runnable {
+
+            private final Sender mSender;
+            private Preparer mPreparer;
+
+            private Token(Sender sender, CancellationSignal signal) {
+                mSender = sender;
+                mPreparer = new Preparer(sender, signal, this);
+            }
+
+            @Override
+            public void close() {
+                Preparer preparer = mPreparer;
+                mPreparer = null;
+                if (preparer != null) {
+                    preparer.setup();
+                }
+            }
+
+            @Override
+            public void run() {
+                mSender.onForget(this);
+            }
+
+            private static class Preparer implements CancellationSignal.OnCancelListener {
+                private final Sender mSender;
+                private final CancellationSignal mSignal;
+                private final Token mToken;
+
+                private Preparer(Sender sender, CancellationSignal signal, Token token) {
+                    mSender = sender;
+                    mSignal = signal;
+                    mToken = token;
+                }
+
+                void setup() {
+                    sCleaner.register(this, mToken);
+                    mSignal.setOnCancelListener(this);
+                }
+
+                @Override
+                public void onCancel() {
+                    try {
+                        mSender.onCancel(mToken);
+                    } finally {
+                        // Make sure we dispatch onCancel before the cleaner can run.
+                        Reference.reachabilityFence(this);
+                    }
+                }
+            }
+        }
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder.
+         *
+         * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
+         */
+        public interface CloseableToken extends IBinder, AutoCloseable {
+            @Override
+            void close(); // No throws
+        }
+    }
+
+    /** The receiving side of a {@link CancellationSignalBeamer}. */
+    public static class Receiver implements IBinder.DeathRecipient {
+        private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>();
+        private final boolean mCancelOnSenderDeath;
+
+        /**
+         * Constructs a new {@code Receiver}.
+         *
+         * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
+         *   {@link #unbeam} are automatically {@link #cancel}led if the sender token
+         *   {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+         *   sending process drops all references to the {@link CancellationSignal} before
+         *   process death, the cancellation is not guaranteed.
+         */
+        public Receiver(boolean cancelOnSenderDeath) {
+            mCancelOnSenderDeath = cancelOnSenderDeath;
+        }
+
+        /**
+         * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a
+         * {@link CancellationSignal}.
+         *
+         * A subsequent call to {@link #cancel} with the same token will cancel the returned
+         * {@code CancellationSignal}.
+         *
+         * @param token a token that was obtained from {@link Sender}, possibly in a remote process.
+         * @return a {@link CancellationSignal} linked to the given token.
+         */
+        @Nullable
+        public CancellationSignal unbeam(@Nullable IBinder token) {
+            if (token == null) {
+                return null;
+            }
+            synchronized (this) {
+                CancellationSignal cs = mTokenMap.get(token);
+                if (cs != null) {
+                    return cs;
+                }
+
+                cs = new CancellationSignal();
+                mTokenMap.put(token, cs);
+                try {
+                    token.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    dead(token);
+                }
+                return cs;
+            }
+        }
+
+        /**
+         * Forgets state associated with the given token (if any).
+         *
+         * Subsequent calls to {@link #cancel} or binder death notifications on the token will not
+         * have any effect.
+         *
+         * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and
+         * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+         *
+         * Optionally, the receiving service logic may also invoke this if it can guarantee that
+         * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+         * using the CancellationSignal has been fully completed).
+         *
+         * @param token the token to forget. No-op if {@code null}.
+         */
+        public void forget(@Nullable IBinder token) {
+            synchronized (this) {
+                if (mTokenMap.remove(token) != null) {
+                    token.unlinkToDeath(this, 0);
+                }
+            }
+        }
+
+        /**
+         * Cancels the {@link CancellationSignal} associated with the given token (if any).
+         *
+         * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and
+         * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+         *
+         * Optionally, the receiving service logic may also invoke this if it can guarantee that
+         * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+         * using the CancellationSignal has been fully completed).
+         *
+         * @param token the token to forget. No-op if {@code null}.
+         */
+        public void cancel(@Nullable IBinder token) {
+            CancellationSignal cs;
+            synchronized (this) {
+                cs = mTokenMap.get(token);
+                if (cs != null) {
+                    forget(token);
+                } else {
+                    return;
+                }
+            }
+            cs.cancel();
+        }
+
+        private void dead(@NonNull IBinder token) {
+            if (mCancelOnSenderDeath) {
+                cancel(token);
+            } else {
+                forget(token);
+            }
+        }
+
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            dead(who);
+        }
+
+        @Override
+        public void binderDied() {
+            throw new RuntimeException("unreachable");
+        }
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index acb1f5b..54e4909 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -116,7 +116,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * The Settings provider contains global system-level device preferences.
@@ -2979,22 +2978,19 @@
     }
 
     private static final class GenerationTracker {
-        @NonNull private final String mName;
-        @NonNull private final MemoryIntArray mArray;
-        @NonNull private final Consumer<String> mErrorHandler;
+        private final MemoryIntArray mArray;
+        private final Runnable mErrorHandler;
         private final int mIndex;
         private int mCurrentGeneration;
 
-        GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
-                int generation, Consumer<String> errorHandler) {
-            mName = name;
+        public GenerationTracker(@NonNull MemoryIntArray array, int index,
+                int generation, Runnable errorHandler) {
             mArray = array;
             mIndex = index;
             mErrorHandler = errorHandler;
             mCurrentGeneration = generation;
         }
 
-        // This method also updates the obsolete generation code stored locally
         public boolean isGenerationChanged() {
             final int currentGeneration = readCurrentGeneration();
             if (currentGeneration >= 0) {
@@ -3015,7 +3011,9 @@
                 return mArray.get(mIndex);
             } catch (IOException e) {
                 Log.e(TAG, "Error getting current generation", e);
-                mErrorHandler.accept(mName);
+                if (mErrorHandler != null) {
+                    mErrorHandler.run();
+                }
             }
             return -1;
         }
@@ -3025,6 +3023,9 @@
                 mArray.close();
             } catch (IOException e) {
                 Log.e(TAG, "Error closing backing array", e);
+                if (mErrorHandler != null) {
+                    mErrorHandler.run();
+                }
             }
         }
     }
@@ -3087,21 +3088,8 @@
         private final ArraySet<String> mAllFields;
         private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
 
-        // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
         @GuardedBy("this")
-        private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
-
-        private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
-            synchronized (NameValueCache.this) {
-                Log.e(TAG, "Error accessing generation tracker - removing");
-                final GenerationTracker tracker = mGenerationTrackers.get(name);
-                if (tracker != null) {
-                    tracker.destroy();
-                    mGenerationTrackers.remove(name);
-                }
-                mValues.remove(name);
-            }
-        };
+        private GenerationTracker mGenerationTracker;
 
         <T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
                 String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
@@ -3190,43 +3178,6 @@
 
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
-            final boolean isSelf = (userHandle == UserHandle.myUserId());
-            int currentGeneration = -1;
-            boolean needsGenerationTracker = false;
-
-            if (isSelf) {
-                synchronized (NameValueCache.this) {
-                    final GenerationTracker generationTracker = mGenerationTrackers.get(name);
-                    if (generationTracker != null) {
-                        if (generationTracker.isGenerationChanged()) {
-                            if (DEBUG) {
-                                Log.i(TAG, "Generation changed for setting:" + name
-                                        + " type:" + mUri.getPath()
-                                        + " in package:" + cr.getPackageName()
-                                        + " and user:" + userHandle);
-                            }
-                            mValues.remove(name);
-                        } else if (mValues.containsKey(name)) {
-                            if (DEBUG) {
-                                Log.i(TAG, "Cache hit for setting:" + name);
-                            }
-                            return mValues.get(name);
-                        }
-                        currentGeneration = generationTracker.getCurrentGeneration();
-                    } else {
-                        needsGenerationTracker = true;
-                    }
-                }
-            } else {
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, "get setting for user " + userHandle
-                            + " by user " + UserHandle.myUserId() + " so skipping cache");
-                }
-            }
-            if (DEBUG) {
-                Log.i(TAG, "Cache miss for setting:" + name);
-            }
-
             // Check if the target settings key is readable. Reject if the caller is not system and
             // is trying to access a settings key defined in the Settings.Secure, Settings.System or
             // Settings.Global and is not annotated as @Readable.
@@ -3260,6 +3211,31 @@
                 }
             }
 
+            final boolean isSelf = (userHandle == UserHandle.myUserId());
+            int currentGeneration = -1;
+            if (isSelf) {
+                synchronized (NameValueCache.this) {
+                    if (mGenerationTracker != null) {
+                        if (mGenerationTracker.isGenerationChanged()) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Generation changed for type:"
+                                        + mUri.getPath() + " in package:"
+                                        + cr.getPackageName() +" and user:" + userHandle);
+                            }
+                            mValues.clear();
+                        } else if (mValues.containsKey(name)) {
+                            return mValues.get(name);
+                        }
+                        if (mGenerationTracker != null) {
+                            currentGeneration = mGenerationTracker.getCurrentGeneration();
+                        }
+                    }
+                }
+            } else {
+                if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
+                        + " by user " + UserHandle.myUserId() + " so skipping cache");
+            }
+
             IContentProvider cp = mProviderHolder.getProvider(cr);
 
             // Try the fast path first, not using query().  If this
@@ -3268,17 +3244,24 @@
             // interface.
             if (mCallGetCommand != null) {
                 try {
-                    Bundle args = new Bundle();
+                    Bundle args = null;
                     if (!isSelf) {
+                        args = new Bundle();
                         args.putInt(CALL_METHOD_USER_KEY, userHandle);
                     }
-                    if (needsGenerationTracker) {
-                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
-                        if (DEBUG) {
-                            Log.i(TAG, "Requested generation tracker for setting:" + name
-                                    + " type:" + mUri.getPath()
-                                    + " in package:" + cr.getPackageName()
-                                    + " and user:" + userHandle);
+                    boolean needsGenerationTracker = false;
+                    synchronized (NameValueCache.this) {
+                        if (isSelf && mGenerationTracker == null) {
+                            needsGenerationTracker = true;
+                            if (args == null) {
+                                args = new Bundle();
+                            }
+                            args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+                            if (DEBUG) {
+                                Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
+                                        + " in package:" + cr.getPackageName() +" and user:"
+                                        + userHandle);
+                            }
                         }
                     }
                     Bundle b;
@@ -3315,24 +3298,33 @@
                                         final int generation = b.getInt(
                                                 CALL_METHOD_GENERATION_KEY, 0);
                                         if (DEBUG) {
-                                            Log.i(TAG, "Received generation tracker for setting:"
-                                                    + name
-                                                    + " type:" + mUri.getPath()
-                                                    + " in package:" + cr.getPackageName()
-                                                    + " and user:" + userHandle
-                                                    + " with index:" + index);
+                                            Log.i(TAG, "Received generation tracker for type:"
+                                                    + mUri.getPath() + " in package:"
+                                                    + cr.getPackageName() + " and user:"
+                                                    + userHandle + " with index:" + index);
                                         }
-                                        mGenerationTrackers.put(name, new GenerationTracker(name,
-                                                array, index, generation,
-                                                mGenerationTrackerErrorHandler));
+                                        if (mGenerationTracker != null) {
+                                            mGenerationTracker.destroy();
+                                        }
+                                        mGenerationTracker = new GenerationTracker(array, index,
+                                                generation, () -> {
+                                            synchronized (NameValueCache.this) {
+                                                Log.e(TAG, "Error accessing generation"
+                                                        + " tracker - removing");
+                                                if (mGenerationTracker != null) {
+                                                    GenerationTracker generationTracker =
+                                                            mGenerationTracker;
+                                                    mGenerationTracker = null;
+                                                    generationTracker.destroy();
+                                                    mValues.clear();
+                                                }
+                                            }
+                                        });
                                         currentGeneration = generation;
                                     }
                                 }
-                                if (mGenerationTrackers.get(name) != null && currentGeneration
-                                        == mGenerationTrackers.get(name).getCurrentGeneration()) {
-                                    if (DEBUG) {
-                                        Log.i(TAG, "Updating cache for setting:" + name);
-                                    }
+                                if (mGenerationTracker != null && currentGeneration ==
+                                        mGenerationTracker.getCurrentGeneration()) {
                                     mValues.put(name, value);
                                 }
                             }
@@ -3375,14 +3367,15 @@
 
                 String value = c.moveToNext() ? c.getString(0) : null;
                 synchronized (NameValueCache.this) {
-                    if (mGenerationTrackers.get(name) != null && currentGeneration
-                            == mGenerationTrackers.get(name).getCurrentGeneration()) {
-                        if (DEBUG) {
-                            Log.i(TAG, "Updating cache for setting:" + name + " using query");
-                        }
+                    if (mGenerationTracker != null
+                            && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
                         mValues.put(name, value);
                     }
                 }
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
+                            name + " = " + (value == null ? "(null)" : value));
+                }
                 return value;
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
@@ -3416,29 +3409,18 @@
             Config.enforceReadPermission(namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
-            boolean needsGenerationTracker = false;
 
             synchronized (NameValueCache.this) {
-                final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
-                if (generationTracker != null) {
-                    if (generationTracker.isGenerationChanged()) {
+                if (mGenerationTracker != null) {
+                    if (mGenerationTracker.isGenerationChanged()) {
                         if (DEBUG) {
-                            Log.i(TAG, "Generation changed for prefix:" + prefix
-                                    + " type:" + mUri.getPath()
+                            Log.i(TAG, "Generation changed for type:" + mUri.getPath()
                                     + " in package:" + cr.getPackageName());
                         }
-                        for (int i = 0; i < mValues.size(); ++i) {
-                            String key = mValues.keyAt(i);
-                            if (key.startsWith(prefix)) {
-                                mValues.remove(key);
-                            }
-                        }
+                        mValues.clear();
                     } else {
                         boolean prefixCached = mValues.containsKey(prefix);
                         if (prefixCached) {
-                            if (DEBUG) {
-                                Log.i(TAG, "Cache hit for prefix:" + prefix);
-                            }
                             if (!names.isEmpty()) {
                                 for (String name : names) {
                                     if (mValues.containsKey(name)) {
@@ -3458,9 +3440,9 @@
                             return keyValues;
                         }
                     }
-                    currentGeneration = generationTracker.getCurrentGeneration();
-                } else {
-                    needsGenerationTracker = true;
+                    if (mGenerationTracker != null) {
+                        currentGeneration = mGenerationTracker.getCurrentGeneration();
+                    }
                 }
             }
 
@@ -3468,20 +3450,20 @@
                 // No list command specified, return empty map
                 return keyValues;
             }
-            if (DEBUG) {
-                Log.i(TAG, "Cache miss for prefix:" + prefix);
-            }
             IContentProvider cp = mProviderHolder.getProvider(cr);
 
             try {
                 Bundle args = new Bundle();
                 args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
-                if (needsGenerationTracker) {
-                    args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
-                    if (DEBUG) {
-                        Log.i(TAG, "Requested generation tracker for prefix:" + prefix
-                                + " type: " + mUri.getPath()
-                                + " in package:" + cr.getPackageName());
+                boolean needsGenerationTracker = false;
+                synchronized (NameValueCache.this) {
+                    if (mGenerationTracker == null) {
+                        needsGenerationTracker = true;
+                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+                        if (DEBUG) {
+                            Log.i(TAG, "Requested generation tracker for type: "
+                                    + mUri.getPath() + " in package:" + cr.getPackageName());
+                        }
                     }
                 }
 
@@ -3534,22 +3516,32 @@
                             final int generation = b.getInt(
                                     CALL_METHOD_GENERATION_KEY, 0);
                             if (DEBUG) {
-                                Log.i(TAG, "Received generation tracker for prefix:" + prefix
-                                        + " type:" + mUri.getPath()
-                                        + " in package:" + cr.getPackageName()
-                                        + " with index:" + index);
+                                Log.i(TAG, "Received generation tracker for type:"
+                                        + mUri.getPath() + " in package:"
+                                        + cr.getPackageName() + " with index:" + index);
                             }
-                            mGenerationTrackers.put(prefix,
-                                    new GenerationTracker(prefix, array, index, generation,
-                                            mGenerationTrackerErrorHandler));
+                            if (mGenerationTracker != null) {
+                                mGenerationTracker.destroy();
+                            }
+                            mGenerationTracker = new GenerationTracker(array, index,
+                                    generation, () -> {
+                                synchronized (NameValueCache.this) {
+                                    Log.e(TAG, "Error accessing generation tracker"
+                                            + " - removing");
+                                    if (mGenerationTracker != null) {
+                                        GenerationTracker generationTracker =
+                                                mGenerationTracker;
+                                        mGenerationTracker = null;
+                                        generationTracker.destroy();
+                                        mValues.clear();
+                                    }
+                                }
+                            });
                             currentGeneration = generation;
                         }
                     }
-                    if (mGenerationTrackers.get(prefix) != null && currentGeneration
-                            == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
-                        if (DEBUG) {
-                            Log.i(TAG, "Updating cache for prefix:" + prefix);
-                        }
+                    if (mGenerationTracker != null && currentGeneration
+                            == mGenerationTracker.getCurrentGeneration()) {
                         // cache the complete list of flags for the namespace
                         mValues.putAll(flagsToValues);
                         // Adding the prefix as a signal that the prefix is cached.
@@ -3565,11 +3557,11 @@
 
         public void clearGenerationTrackerForTest() {
             synchronized (NameValueCache.this) {
-                for (int i = 0; i < mGenerationTrackers.size(); i++) {
-                    mGenerationTrackers.valueAt(i).destroy();
+                if (mGenerationTracker != null) {
+                    mGenerationTracker.destroy();
                 }
-                mGenerationTrackers.clear();
                 mValues.clear();
+                mGenerationTracker = null;
             }
         }
     }
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
index 81b9f22..1ad0424 100644
--- a/core/java/android/service/credentials/BeginGetCredentialOption.java
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -116,12 +116,12 @@
      * @param id the unique id associated with this option
      * @param type               the requested credential type
      * @param candidateQueryData the request candidateQueryData
-     * @throws IllegalArgumentException If type is empty.
+     * @throws IllegalArgumentException If id or type is empty.
      */
     public BeginGetCredentialOption(
             @NonNull String id, @NonNull String type,
             @NonNull Bundle candidateQueryData) {
-        mId = id;
+        mId = Preconditions.checkStringNotEmpty(id, "id must not be empty");
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
         Bundle bundle = new Bundle();
         bundle.putAll(candidateQueryData);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 12cd523..f53abce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2198,6 +2198,11 @@
                 }
                 mCreated = false;
             }
+
+            if (mSurfaceControl != null) {
+                mSurfaceControl.release();
+                mSurfaceControl = null;
+            }
         }
 
         private final DisplayListener mDisplayListener = new DisplayListener() {
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 98c0d7f..5aa0f59 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -477,7 +477,14 @@
         return mTimeResult;
     }
 
-    /** Clears the last received NTP. Intended for use during tests. */
+    /** Sets the last received NTP time. Intended for use during tests. */
+    public void setCachedTimeResult(TimeResult timeResult) {
+        synchronized (this) {
+            mTimeResult = timeResult;
+        }
+    }
+
+    /** Clears the last received NTP time. Intended for use during tests. */
     public void clearCachedTimeResult() {
         synchronized (this) {
             mTimeResult = null;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 20be9d6..5476088 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1261,20 +1261,17 @@
 
     /**
      * @hide
-     * Returns the display's HDR supported types.
+     * Returns the current mode's supported HDR types.
      *
      * @see #isHdr()
-     * @see HdrCapabilities#getSupportedHdrTypes()
+     * @see Mode#getSupportedHdrTypes()
      */
     @TestApi
     @NonNull
     public int[] getReportedHdrTypes() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            if (mDisplayInfo.hdrCapabilities == null) {
-                return new int[0];
-            }
-            return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+            return mDisplayInfo.getMode().getSupportedHdrTypes();
         }
     }
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bdc7333..aef0e65 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -899,9 +899,10 @@
 
         // 3. Get the activity names substring between the indexes
         final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
-        if (activityStringStartIndex < firstNextSemicolonIndex) {
+        if (activityStringStartIndex >= firstNextSemicolonIndex) {
             Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
                     + "formatted");
+            return;
         }
         final String activitySubstring =
                 denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index db17a53..d84acc0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -25,6 +25,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -33,6 +34,7 @@
 import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -40,7 +42,6 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
@@ -581,51 +582,57 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */
     @AnyThread
-    @Nullable
-    static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
+    @NonNull
+    static ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
-            return null;
+            // Create token with "fake" binder if the service was not found.
+            return new ImeTracker.Token(new Binder(), tag);
         }
         try {
-            return service.onRequestShow(uid, origin, reason);
+            return service.onRequestShow(tag, uid, origin, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */
     @AnyThread
-    @Nullable
-    static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
+    @NonNull
+    static ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
-            return null;
+            // Create token with "fake" binder if the service was not found.
+            return new ImeTracker.Token(new Binder(), tag);
         }
         try {
-            return service.onRequestHide(uid, origin, reason);
+            return service.onRequestHide(tag, uid, origin, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */
     @AnyThread
-    static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
         }
         try {
-            service.onProgress(statsToken, phase);
+            service.onProgress(binder, phase);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */
     @AnyThread
-    static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -637,8 +644,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */
     @AnyThread
-    static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -650,8 +658,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onShown */
     @AnyThread
-    static void onShown(@NonNull IBinder statsToken) {
+    static void onShown(@NonNull ImeTracker.Token statsToken) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -663,8 +672,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */
     @AnyThread
-    static void onHidden(@NonNull IBinder statsToken) {
+    static void onHidden(@NonNull ImeTracker.Token statsToken) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -676,6 +686,7 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
     @AnyThread
     @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     static boolean hasPendingImeVisibilityRequests() {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index e5a99ff..f0d1019 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -26,7 +26,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -47,7 +46,7 @@
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /** @hide */
@@ -321,9 +320,8 @@
     /**
      * Creates an IME show request tracking token.
      *
-     * @param component the component name where the IME show request was created,
-     *                  or {@code null} otherwise
-     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param component the name of the component that created the IME request, or {@code null}
+     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
@@ -337,9 +335,8 @@
     /**
      * Creates an IME hide request tracking token.
      *
-     * @param component the component name where the IME hide request was created,
-     *                  or {@code null} otherwise
-     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param component the name of the component that created the IME request, or {@code null}
+     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
@@ -435,8 +432,7 @@
             mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
             // Update logging flag dynamically.
             SystemProperties.addChangeCallback(() ->
-                    mLogProgress =
-                            SystemProperties.getBoolean("persist.debug.imetracker", false));
+                    mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false));
         }
 
         /** Whether progress should be logged. */
@@ -446,10 +442,9 @@
         @Override
         public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
-            if (binder == null) binder = new Binder();
-
-            final Token token = Token.build(binder, component);
+            final var tag = getTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin,
+                    reason);
 
             Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -461,10 +456,9 @@
         @Override
         public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
-            if (binder == null) binder = new Binder();
-
-            final Token token = Token.build(binder, component);
+            final var tag = getTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin,
+                    reason);
 
             Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -485,7 +479,7 @@
         @Override
         public void onFailed(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+            IInputMethodManagerGlobalInvoker.onFailed(token, phase);
 
             Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
         }
@@ -499,7 +493,7 @@
         @Override
         public void onCancelled(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+            IInputMethodManagerGlobalInvoker.onCancelled(token, phase);
 
             Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
         }
@@ -507,7 +501,7 @@
         @Override
         public void onShown(@Nullable Token token) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+            IInputMethodManagerGlobalInvoker.onShown(token);
 
             Log.i(TAG, token.mTag + ": onShown");
         }
@@ -515,10 +509,24 @@
         @Override
         public void onHidden(@Nullable Token token) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+            IInputMethodManagerGlobalInvoker.onHidden(token);
 
             Log.i(TAG, token.mTag + ": onHidden");
         }
+
+        /**
+         * Returns a logging tag using the given component name.
+         *
+         * @param component the name of the component that created the IME request, or {@code null}
+         *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
+         */
+        @NonNull
+        private String getTag(@Nullable String component) {
+            if (component == null) {
+                component = ActivityThread.currentProcessName();
+            }
+            return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+        }
     };
 
     /** The singleton IME tracker instance for instrumenting jank metrics. */
@@ -528,28 +536,31 @@
     ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
 
     /** A token that tracks the progress of an IME request. */
-    class Token implements Parcelable {
+    final class Token implements Parcelable {
 
+        /** The binder used to identify this token. */
         @NonNull
-        public final IBinder mBinder;
+        private final IBinder mBinder;
 
+        /** Logging tag, of the shape "component:random_hexadecimal". */
         @NonNull
         private final String mTag;
 
-        @NonNull
-        private static Token build(@NonNull IBinder binder, @Nullable String component) {
-            if (component == null) component = ActivityThread.currentProcessName();
-            final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
-
-            return new Token(binder, tag);
-        }
-
-        private Token(@NonNull IBinder binder, @NonNull String tag) {
+        public Token(@NonNull IBinder binder, @NonNull String tag) {
             mBinder = binder;
             mTag = tag;
         }
 
-        /** Returns the {@link Token#mTag} */
+        private Token(@NonNull Parcel in) {
+            mBinder = in.readStrongBinder();
+            mTag = in.readString8();
+        }
+
+        @NonNull
+        public IBinder getBinder() {
+            return mBinder;
+        }
+
         @NonNull
         public String getTag() {
             return mTag;
@@ -562,7 +573,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeStrongBinder(mBinder);
             dest.writeString8(mTag);
         }
@@ -571,12 +582,11 @@
         public static final Creator<Token> CREATOR = new Creator<>() {
             @NonNull
             @Override
-            public Token createFromParcel(Parcel source) {
-                final IBinder binder = source.readStrongBinder();
-                final String tag = source.readString8();
-                return new Token(binder, tag);
+            public Token createFromParcel(@NonNull Parcel in) {
+                return new Token(in);
             }
 
+            @NonNull
             @Override
             public Token[] newArray(int size) {
                 return new Token[size];
@@ -589,40 +599,50 @@
      *
      * Note: This is held in a separate class so that it only gets initialized when actually needed.
      */
-    class Debug {
+    final class Debug {
 
+        @NonNull
         private static final Map<Integer, String> sTypes =
                 getFieldMapping(ImeTracker.class, "TYPE_");
+        @NonNull
         private static final Map<Integer, String> sStatus =
                 getFieldMapping(ImeTracker.class, "STATUS_");
+        @NonNull
         private static final Map<Integer, String> sOrigins =
                 getFieldMapping(ImeTracker.class, "ORIGIN_");
+        @NonNull
         private static final Map<Integer, String> sPhases =
                 getFieldMapping(ImeTracker.class, "PHASE_");
 
+        @NonNull
         public static String typeToString(@Type int type) {
             return sTypes.getOrDefault(type, "TYPE_" + type);
         }
 
+        @NonNull
         public static String statusToString(@Status int status) {
             return sStatus.getOrDefault(status, "STATUS_" + status);
         }
 
+        @NonNull
         public static String originToString(@Origin int origin) {
             return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
         }
 
+        @NonNull
         public static String phaseToString(@Phase int phase) {
             return sPhases.getOrDefault(phase, "PHASE_" + phase);
         }
 
-        private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+        @NonNull
+        private static Map<Integer, String> getFieldMapping(Class<?> cls,
+                @NonNull String fieldPrefix) {
             return Arrays.stream(cls.getDeclaredFields())
                     .filter(field -> field.getName().startsWith(fieldPrefix))
                     .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
         }
 
-        private static int getFieldValue(Field field) {
+        private static int getFieldValue(@NonNull Field field) {
             try {
                 return field.getInt(null);
             } catch (IllegalAccessException e) {
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index ec1badb..8b55494 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -83,6 +84,22 @@
     public static final String ACTION_STYLUS_HANDWRITING_SETTINGS =
             "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
 
+    /**
+     * Maximal length of a component name
+     * @hide
+     */
+    @TestApi
+    public static final int COMPONENT_NAME_MAX_LENGTH = 1000;
+
+    /**
+     * The maximum amount of IMEs that are loaded per package (in order).
+     * If a package contains more IMEs, they will be ignored and cannot be enabled.
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("MinMaxConstant")
+    public static final int MAX_IMES_PER_PACKAGE = 20;
+
     static final String TAG = "InputMethodInfo";
 
     /**
@@ -252,6 +269,13 @@
                     com.android.internal.R.styleable.InputMethod);
             settingsActivityComponent = sa.getString(
                     com.android.internal.R.styleable.InputMethod_settingsActivity);
+            if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) || (
+                    settingsActivityComponent != null
+                            && settingsActivityComponent.length() > COMPONENT_NAME_MAX_LENGTH)) {
+                throw new XmlPullParserException(
+                        "Activity name exceeds maximum of 1000 characters");
+            }
+
             isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false);
             isDefaultResId = sa.getResourceId(
                     com.android.internal.R.styleable.InputMethod_isDefault, 0);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b89dd31..9f9a781 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -52,6 +52,7 @@
 import android.graphics.RenderNode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -3238,6 +3239,44 @@
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
 
         mPreserveSelection = true;
+
+        // No-op for the old context menu because it doesn't have icons.
+        adjustIconSpacing(menu);
+    }
+
+    /**
+     * Adjust icon spacing to align the texts.
+     * @hide
+     */
+    @VisibleForTesting
+    public void adjustIconSpacing(ContextMenu menu) {
+        int width = -1;
+        int height = -1;
+        for (int i = 0; i < menu.size(); ++i) {
+            final MenuItem item = menu.getItem(i);
+            final Drawable d = item.getIcon();
+            if (d == null) {
+                continue;
+            }
+
+            width = Math.max(width, d.getIntrinsicWidth());
+            height = Math.max(height, d.getIntrinsicHeight());
+        }
+
+        if (width < 0 || height < 0) {
+            return;  // No menu has icon drawable.
+        }
+
+        GradientDrawable paddingDrawable = new GradientDrawable();
+        paddingDrawable.setSize(width, height);
+
+        for (int i = 0; i < menu.size(); ++i) {
+            final MenuItem item = menu.getItem(i);
+            final Drawable d = item.getIcon();
+            if (d == null) {
+                item.setIcon(paddingDrawable);
+            }
+        }
     }
 
     @Nullable
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 7c237e69..ace8451 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -21,6 +21,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.content.ContentProvider.getUserIdFromUri;
 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
 
@@ -161,6 +162,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * The Chooser Activity handles intent resolution specifically for sharing intents -
@@ -1397,7 +1399,7 @@
 
             ImageView previewThumbnailView = contentPreviewLayout.findViewById(
                     R.id.content_preview_thumbnail);
-            if (previewThumbnail == null) {
+            if (!validForContentPreview(previewThumbnail)) {
                 previewThumbnailView.setVisibility(View.GONE);
             } else {
                 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
@@ -1427,6 +1429,10 @@
         String action = targetIntent.getAction();
         if (Intent.ACTION_SEND.equals(action)) {
             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM, android.net.Uri.class);
+            if (!validForContentPreview(uri)) {
+                imagePreview.setVisibility(View.GONE);
+                return contentPreviewLayout;
+            }
             imagePreview.findViewById(R.id.content_preview_image_1_large)
                     .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
             mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
@@ -1436,7 +1442,7 @@
             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, android.net.Uri.class);
             List<Uri> imageUris = new ArrayList<>();
             for (Uri uri : uris) {
-                if (isImageType(resolver.getType(uri))) {
+                if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) {
                     imageUris.add(uri);
                 }
             }
@@ -1546,9 +1552,16 @@
         String action = targetIntent.getAction();
         if (Intent.ACTION_SEND.equals(action)) {
             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM, android.net.Uri.class);
+            if (!validForContentPreview(uri)) {
+                contentPreviewLayout.setVisibility(View.GONE);
+                return contentPreviewLayout;
+            }
             loadFileUriIntoView(uri, contentPreviewLayout);
         } else {
             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, android.net.Uri.class);
+            uris = uris.stream()
+                    .filter(ChooserActivity::validForContentPreview)
+                    .collect(Collectors.toList());
             int uriCount = uris.size();
 
             if (uriCount == 0) {
@@ -1607,6 +1620,24 @@
         }
     }
 
+    /**
+     * Indicate if the incoming content URI should be allowed.
+     *
+     * @param uri the uri to test
+     * @return true if the URI is allowed for content preview
+     */
+    private static boolean validForContentPreview(Uri uri) throws SecurityException {
+        if (uri == null) {
+            return false;
+        }
+        int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+        if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) {
+            Log.e(TAG, "dropped invalid content URI belonging to user " + userId);
+            return false;
+        }
+        return true;
+    }
+
     @VisibleForTesting
     protected boolean isImageType(String mimeType) {
         return mimeType != null && mimeType.startsWith("image/");
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
similarity index 71%
rename from core/java/com/android/internal/view/IImeTracker.aidl
rename to core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b062ca7..c7418ee 100644
--- a/core/java/com/android/internal/view/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -14,43 +14,45 @@
  * limitations under the License.
  */
 
-package com.android.internal.view;
+package com.android.internal.inputmethod;
 
 import android.view.inputmethod.ImeTracker;
 
 /**
- * Interface to the global Ime tracker, used by all client applications.
+ * Interface to the global IME tracker service, used by all client applications.
  * {@hide}
  */
 interface IImeTracker {
 
     /**
-     * Called when an IME show request is created,
-     * returns a new Binder to be associated with the IME tracking token.
+     * Called when an IME show request is created.
      *
+     * @param tag the logging tag.
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
+     * @return A new IME tracking token.
      */
-    IBinder onRequestShow(int uid, int origin, int reason);
+    ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason);
 
     /**
-     * Called when an IME hide request is created,
-     * returns a new Binder to be associated with the IME tracking token.
+     * Called when an IME hide request is created.
      *
+     * @param tag the logging tag.
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
+     * @return A new IME tracking token.
      */
-    IBinder onRequestHide(int uid, int origin, int reason);
+    ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason);
 
     /**
      * Called when the IME request progresses to a further phase.
      *
-     * @param statsToken the token tracking the current IME request.
+     * @param binder the binder of token tracking the current IME request.
      * @param phase the new phase the IME request reached.
      */
-    oneway void onProgress(in IBinder statsToken, int phase);
+    oneway void onProgress(in IBinder binder, int phase);
 
     /**
      * Called when the IME request fails.
@@ -58,7 +60,7 @@
      * @param statsToken the token tracking the current IME request.
      * @param phase the phase the IME request failed at.
      */
-    oneway void onFailed(in IBinder statsToken, int phase);
+    oneway void onFailed(in ImeTracker.Token statsToken, int phase);
 
     /**
      * Called when the IME request is cancelled.
@@ -66,21 +68,21 @@
      * @param statsToken the token tracking the current IME request.
      * @param phase the phase the IME request was cancelled at.
      */
-    oneway void onCancelled(in IBinder statsToken, int phase);
+    oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
 
     /**
      * Called when the IME show request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
-    oneway void onShown(in IBinder statsToken);
+    oneway void onShown(in ImeTracker.Token statsToken);
 
     /**
      * Called when the IME hide request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
-    oneway void onHidden(in IBinder statsToken);
+    oneway void onHidden(in ImeTracker.Token statsToken);
 
     /**
      * Checks whether there are any pending IME visibility requests.
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 5805d0e..9a4610e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -23,11 +23,11 @@
 import android.view.inputmethod.EditorInfo;
 import android.window.ImeOnBackInvokedDispatcher;
 
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.view.IImeTracker;
 
 /**
  * Public interface to the global input method manager, used by all client
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 12eff67..2f94ed7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3065,6 +3065,10 @@
     <string name="config_credentialManagerDialogComponent" translatable="false"
             >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
 
+    <!-- Name of the broadcast receiver that is used to receive provider change events -->
+    <string name="config_credentialManagerReceiverComponent" translatable="false"
+            >com.android.credentialmanager/com.android.credentialmanager.CredentialProviderReceiver</string>
+
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e89fc8..12646a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2210,6 +2210,7 @@
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
   <java-symbol type="string" name="config_credentialManagerDialogComponent" />
+  <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
   <java-symbol type="string" name="config_persistentDataPackageName" />
   <java-symbol type="string" name="config_deviceConfiguratorPackageName" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index e164e08..4f91e7a 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -16,7 +16,7 @@
 
 package android.app.activity;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
+import static android.content.Context.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_EDIT;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index 324f810..d478437 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -16,7 +16,7 @@
 
 package android.content;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.view.Display.DEFAULT_DISPLAY;
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
new file mode 100644
index 0000000..42c97f3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.os.CancellationSignalBeamer.Sender;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.CancellationSignalBeamer.Receiver;
+import android.util.PollingCheck;
+import android.util.PollingCheck.PollingCheckCondition;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CancellationSignalBeamerTest {
+
+    private CancellationSignal mSenderSignal = new CancellationSignal();
+    private CancellationSignal mReceivedSignal;
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testBeam_null() {
+        try (var token = mSender.beam(null)) {
+            assertThat(token).isNull();
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal).isNull();
+    }
+
+    @Test
+    public void testBeam_nonNull() {
+        try (var token = mSender.beam(mSenderSignal)) {
+            assertThat(token).isNotNull();
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal).isNotNull();
+    }
+
+    @Test
+    public void testBeam_async() {
+        IBinder outerToken;
+        try (var token = mSender.beam(mSenderSignal)) {
+            assertThat(token).isNotNull();
+            outerToken = token;
+        }
+        invokeGenericService(outerToken);
+        assertThat(mReceivedSignal).isNotNull();
+    }
+
+    @Test
+    public void testCancelOnSentSignal_cancelsReceivedSignal() {
+        try (var token = mSender.beam(mSenderSignal)) {
+            invokeGenericService(token);
+        }
+        mSenderSignal.cancel();
+        assertThat(mReceivedSignal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testSendingCancelledSignal_cancelsReceivedSignal() {
+        mSenderSignal.cancel();
+        try (var token = mSender.beam(mSenderSignal)) {
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testUnbeam_null() {
+        assertThat(mReceiver.unbeam(null)).isNull();
+    }
+
+    @Test
+    public void testForget_null() {
+        mReceiver.forget(null);
+    }
+
+    @Test
+    public void testCancel_null() {
+        mReceiver.cancel(null);
+    }
+
+    @Test
+    public void testForget_withUnknownToken() {
+        mReceiver.forget(new Binder());
+    }
+
+    @Test
+    public void testCancel_withUnknownToken() {
+        mReceiver.cancel(new Binder());
+    }
+
+    @Test
+    public void testBinderDied_withUnknownToken() {
+        mReceiver.binderDied(new Binder());
+    }
+
+    @Test
+    public void testReceiverWithCancelOnSenderDead_cancelsOnSenderDeath() {
+        var receiver = new Receiver(true /* cancelOnSenderDeath */);
+        var token = new Binder();
+        var signal = receiver.unbeam(token);
+        receiver.binderDied(token);
+        assertThat(signal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testReceiverWithoutCancelOnSenderDead_doesntCancelOnSenderDeath() {
+        var receiver = new Receiver(false /* cancelOnSenderDeath */);
+        var token = new Binder();
+        var signal = receiver.unbeam(token);
+        receiver.binderDied(token);
+        assertThat(signal.isCanceled()).isFalse();
+    }
+
+    @Test
+    public void testDroppingSentSignal_dropsReceivedSignal() throws Exception {
+        // In a multiprocess scenario, sending token over Binder might leak the token
+        // on both ends if we create a reference cycle. Simulate that worst-case scenario
+        // here by leaking it directly, then test that cleanup of the signals still works.
+        var receivedSignalCleaned = new CountDownLatch(1);
+        var tokenRef = new Object[1];
+        // Reference the cancellation signals in a separate method scope, so we don't
+        // accidentally leak them on the stack / in a register.
+        Runnable r = () -> {
+            try (var token = mSender.beam(mSenderSignal)) {
+                tokenRef[0] = token;
+                invokeGenericService(token);
+            }
+            mSenderSignal = null;
+
+            Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+            mReceivedSignal = null;
+        };
+        r.run();
+
+        waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+        Reference.reachabilityFence(tokenRef[0]);
+    }
+
+    @Test
+    public void testRepeatedBeaming_doesntLeak() throws Exception {
+        var receivedSignalCleaned = new CountDownLatch(1);
+        var tokenRef = new Object[1];
+        // Reference the cancellation signals in a separate method scope, so we don't
+        // accidentally leak them on the stack / in a register.
+        Runnable r = () -> {
+            try (var token = mSender.beam(mSenderSignal)) {
+                tokenRef[0] = token;
+                invokeGenericService(token);
+            }
+            // Beaming again leaves mReceivedSignal dangling, so it should be collected.
+            mSender.beam(mSenderSignal).close();
+
+            Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+            mReceivedSignal = null;
+        };
+        r.run();
+
+        waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+        Reference.reachabilityFence(tokenRef[0]);
+    }
+
+    private void waitForWithGc(PollingCheckCondition condition) throws IOException {
+        try {
+            PollingCheck.waitFor(() -> {
+                Runtime.getRuntime().gc();
+                return condition.canProceed();
+            });
+        } catch (AssertionError e) {
+            File heap = new File(mContext.getExternalFilesDir(null), "dump.hprof");
+            Debug.dumpHprofData(heap.getAbsolutePath());
+            throw e;
+        }
+    }
+
+    private void invokeGenericService(IBinder cancellationSignalToken) {
+        mReceivedSignal = mReceiver.unbeam(cancellationSignalToken);
+    }
+
+    private final Sender mSender = new Sender() {
+        @Override
+        public void onCancel(IBinder token) {
+            mReceiver.cancel(token);
+        }
+
+        @Override
+        public void onForget(IBinder token) {
+            mReceiver.forget(token);
+        }
+    };
+
+    private final Receiver mReceiver = new Receiver(false);
+}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index b6fc137..2e31bb5 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -32,18 +32,16 @@
 import android.test.mock.MockContentResolver;
 import android.util.MemoryIntArray;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -61,63 +59,42 @@
     private static final String NAMESPACE = "namespace";
     private static final String NAMESPACE2 = "namespace2";
 
-    private static final String SETTING = "test_setting";
-    private static final String SETTING2 = "test_setting2";
-
     @Mock
     private IContentProvider mMockIContentProvider;
     @Mock
     private ContentProvider mMockContentProvider;
     private MockContentResolver mMockContentResolver;
-    private MemoryIntArray mConfigsCacheGenerationStore;
-    private MemoryIntArray mSettingsCacheGenerationStore;
+    private MemoryIntArray mCacheGenerationStore;
+    private int mCurrentGeneration = 123;
 
-    private HashMap<String, HashMap<String, String>> mConfigsStorage;
-    private HashMap<String, String> mSettingsStorage;
-
+    private HashMap<String, HashMap<String, String>> mStorage;
 
     @Before
     public void setUp() throws Exception {
         Settings.Config.clearProviderForTest();
-        Settings.Secure.clearProviderForTest();
         MockitoAnnotations.initMocks(this);
         when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
-        mMockContentResolver = new MockContentResolver(
-                InstrumentationRegistry.getInstrumentation().getContext());
+        mMockContentResolver = new MockContentResolver(InstrumentationRegistry
+                .getInstrumentation().getContext());
         mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
                 mMockContentProvider);
-        mMockContentResolver.addProvider(Settings.Secure.CONTENT_URI.getAuthority(),
-                mMockContentProvider);
-        mConfigsCacheGenerationStore = new MemoryIntArray(2);
-        mConfigsCacheGenerationStore.set(0, 123);
-        mConfigsCacheGenerationStore.set(1, 456);
-        mSettingsCacheGenerationStore = new MemoryIntArray(2);
-        mSettingsCacheGenerationStore.set(0, 234);
-        mSettingsCacheGenerationStore.set(1, 567);
-        mConfigsStorage = new HashMap<>();
-        mSettingsStorage = new HashMap<>();
+        mCacheGenerationStore = new MemoryIntArray(1);
+        mStorage = new HashMap<>();
 
         // Stores keyValues for a given prefix and increments the generation. (Note that this
         // increments the generation no matter what, it doesn't pay attention to if anything
         // actually changed).
         when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class))).thenAnswer(
-                invocationOnMock -> {
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+                any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
                     HashMap<String, String> keyValues =
                             (HashMap<String, String>) incomingBundle.getSerializable(
-                                    Settings.CALL_METHOD_FLAGS_KEY, HashMap.class);
+                                    Settings.CALL_METHOD_FLAGS_KEY);
                     String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
-                    mConfigsStorage.put(prefix, keyValues);
-                    int currentGeneration;
-                    // Different prefixes have different generation codes
-                    if (prefix.equals(NAMESPACE + "/")) {
-                        currentGeneration = mConfigsCacheGenerationStore.get(0);
-                        mConfigsCacheGenerationStore.set(0, ++currentGeneration);
-                    } else if (prefix.equals(NAMESPACE2 + "/")) {
-                        currentGeneration = mConfigsCacheGenerationStore.get(1);
-                        mConfigsCacheGenerationStore.set(1, ++currentGeneration);
-                    }
+                    mStorage.put(prefix, keyValues);
+                    mCacheGenerationStore.set(0, ++mCurrentGeneration);
+
                     Bundle result = new Bundle();
                     result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
                             Settings.SET_ALL_RESULT_SUCCESS);
@@ -125,87 +102,32 @@
                 });
 
         // Returns the keyValues corresponding to a namespace, or an empty map if the namespace
-        // doesn't have anything stored for it. Returns the generation key if the map isn't empty
-        // and the caller asked for the generation key.
+        // doesn't have anything stored for it. Returns the generation key if the caller asked
+        // for one.
         when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class))).thenAnswer(
-                invocationOnMock -> {
+                eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
 
                     String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
 
-                    if (!mConfigsStorage.containsKey(prefix)) {
-                        mConfigsStorage.put(prefix, new HashMap<>());
+                    if (!mStorage.containsKey(prefix)) {
+                        mStorage.put(prefix, new HashMap<>());
                     }
-                    HashMap<String, String> keyValues = mConfigsStorage.get(prefix);
+                    HashMap<String, String> keyValues = mStorage.get(prefix);
 
                     Bundle bundle = new Bundle();
                     bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
 
-                    if (!keyValues.isEmpty() && incomingBundle.containsKey(
-                            Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
-                        int index = prefix.equals(NAMESPACE + "/") ? 0 : 1;
+                    if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
                         bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                mConfigsCacheGenerationStore);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+                                mCacheGenerationStore);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
                         bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                                mConfigsCacheGenerationStore.get(index));
+                                mCacheGenerationStore.get(0));
                     }
                     return bundle;
                 });
-
-        // Stores value for a given setting's name and increments the generation. (Note that this
-        // increments the generation no matter what, it doesn't pay attention to if anything
-        // actually changed).
-        when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class))).thenAnswer(
-                invocationOnMock -> {
-                    Bundle incomingBundle = invocationOnMock.getArgument(4);
-                    String key = invocationOnMock.getArgument(3);
-                    String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
-                    mSettingsStorage.put(key, value);
-                    int currentGeneration;
-                    // Different settings have different generation codes
-                    if (key.equals(SETTING)) {
-                        currentGeneration = mSettingsCacheGenerationStore.get(0);
-                        mSettingsCacheGenerationStore.set(0, ++currentGeneration);
-                    } else if (key.equals(SETTING2)) {
-                        currentGeneration = mSettingsCacheGenerationStore.get(1);
-                        mSettingsCacheGenerationStore.set(1, ++currentGeneration);
-                    }
-                    return null;
-                });
-
-        // Returns the value corresponding to a setting, or null if the setting
-        // doesn't have a value stored for it. Returns the generation key if the value isn't null
-        // and the caller asked for the generation key.
-        when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
-                invocationOnMock -> {
-                    Bundle incomingBundle = invocationOnMock.getArgument(4);
-                    String key = invocationOnMock.getArgument(3);
-                    String value = mSettingsStorage.get(key);
-
-                    Bundle bundle = new Bundle();
-                    bundle.putSerializable(Settings.NameValueTable.VALUE, value);
-
-                    if (value != null && incomingBundle.containsKey(
-                            Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
-                        int index = key.equals(SETTING) ? 0 : 1;
-                        bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                mSettingsCacheGenerationStore);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                                mSettingsCacheGenerationStore.get(index));
-                    }
-                    return bundle;
-                });
-    }
-
-    @After
-    public void cleanUp() throws IOException {
-        mConfigsStorage.clear();
-        mSettingsStorage.clear();
     }
 
     @Test
@@ -213,13 +135,16 @@
         HashMap<String, String> keyValues = new HashMap<>();
         keyValues.put("a", "b");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
+        verify(mMockIContentProvider).call(any(), any(),
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+                any(), any(Bundle.class));
 
         Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE, Collections.emptyList());
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
+                NAMESPACE,
+                Collections.emptyList());
+        verify(mMockIContentProvider).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
         assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
@@ -231,12 +156,14 @@
         keyValues.put("a", "c");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+                any(), any(Bundle.class));
 
         Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
         assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
@@ -250,31 +177,36 @@
         HashMap<String, String> keyValues = new HashMap<>();
         keyValues.put("a", "b");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
-        verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+        verify(mMockIContentProvider).call(any(), any(),
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
                 any(), any(Bundle.class));
 
-        Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE, Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
-        assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
-
         HashMap<String, String> keyValues2 = new HashMap<>();
         keyValues2.put("c", "d");
         keyValues2.put("e", "f");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+                any(), any(Bundle.class));
+
+        Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
+                NAMESPACE,
+                Collections.emptyList());
+        verify(mMockIContentProvider).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
+        assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE2, Collections.emptyList());
+                NAMESPACE2,
+                Collections.emptyList());
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
         assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
-        // Modifying the second namespace doesn't affect the cache of the first namespace
         verifyNoMoreInteractions(mMockIContentProvider);
         assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
 
@@ -287,90 +219,17 @@
     @Test
     public void testCaching_emptyNamespace() throws Exception {
         Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE, Collections.emptyList());
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
+                NAMESPACE,
+                Collections.emptyList());
+        verify(mMockIContentProvider).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
         assertThat(returnedValues).isEmpty();
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
-        // Empty list won't be cached
-        verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
+        verifyNoMoreInteractions(mMockIContentProvider);
         assertThat(cachedKeyValues).isEmpty();
     }
 
-    @Test
-    public void testCaching_singleSetting() throws Exception {
-        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
-
-        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(returnedValue).isEqualTo("a");
-
-        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        verifyNoMoreInteractions(mMockIContentProvider);
-        assertThat(cachedValue).isEqualTo("a");
-
-        // Modify the value to invalidate the cache.
-        Settings.Secure.putString(mMockContentResolver, SETTING, "b");
-        verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
-
-        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
-        verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(returnedValue2).isEqualTo("b");
-
-        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
-        verifyNoMoreInteractions(mMockIContentProvider);
-        assertThat(cachedValue2).isEqualTo("b");
-    }
-
-    @Test
-    public void testCaching_multipleSettings() throws Exception {
-        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
-
-        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(returnedValue).isEqualTo("a");
-
-        Settings.Secure.putString(mMockContentResolver, SETTING2, "b");
-        verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
-
-        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
-        verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(returnedValue2).isEqualTo("b");
-
-        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        // Modifying the second setting doesn't affect the cache of the first setting
-        verifyNoMoreInteractions(mMockIContentProvider);
-        assertThat(cachedValue).isEqualTo("a");
-
-        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
-        verifyNoMoreInteractions(mMockIContentProvider);
-        assertThat(cachedValue2).isEqualTo("b");
-    }
-
-    @Test
-    public void testCaching_nullSetting() throws Exception {
-        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        verify(mMockIContentProvider, times(1)).call(any(), any(),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(returnedValue).isNull();
-
-        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        // Empty list won't be cached
-        verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(cachedValue).isNull();
-    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 0c7550e..777246b 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -33,6 +33,8 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.view.ContextMenu;
 import android.view.MenuItem;
@@ -167,4 +169,75 @@
         assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST);
         assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE);
     }
+
+    @UiThreadTest
+    @Test
+    public void testAdjustIconSpaces() {
+        GradientDrawable gd = new GradientDrawable();
+        gd.setSize(128, 256);
+
+        // Setup mocks
+        ContextMenu menu = mock(ContextMenu.class);
+
+        MenuItem mockIconMenu = newMockMenuItem();
+        when(mockIconMenu.getIcon()).thenReturn(gd);
+
+        MenuItem mockNoIconMenu = newMockMenuItem();
+        when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+        MenuItem mockNoIconMenu2 = newMockMenuItem();
+        when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+        when(menu.size()).thenReturn(3);
+        when(menu.getItem(0)).thenReturn(mockIconMenu);
+        when(menu.getItem(1)).thenReturn(mockNoIconMenu);
+        when(menu.getItem(2)).thenReturn(mockNoIconMenu2);
+
+
+        // Execute the test method
+        EditText et = mActivity.findViewById(R.id.editText);
+        Editor editor = et.getEditorForTesting();
+        editor.adjustIconSpacing(menu);
+
+        // Verify
+        ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);
+        verify(mockNoIconMenu).setIcon(drawableCaptor.capture());
+
+        Drawable paddingDrawable = drawableCaptor.getValue();
+        assertThat(paddingDrawable).isNotNull();
+        assertThat(paddingDrawable.getIntrinsicWidth()).isEqualTo(128);
+        assertThat(paddingDrawable.getIntrinsicHeight()).isEqualTo(256);
+
+        ArgumentCaptor<Drawable> drawableCaptor2 = ArgumentCaptor.forClass(Drawable.class);
+        verify(mockNoIconMenu2).setIcon(drawableCaptor2.capture());
+
+        Drawable paddingDrawable2 = drawableCaptor2.getValue();
+        assertThat(paddingDrawable2).isSameInstanceAs(paddingDrawable);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAdjustIconSpacesNoIconCase() {
+        // Setup mocks
+        ContextMenu menu = mock(ContextMenu.class);
+
+        MenuItem mockNoIconMenu = newMockMenuItem();
+        when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+        MenuItem mockNoIconMenu2 = newMockMenuItem();
+        when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+        when(menu.size()).thenReturn(2);
+        when(menu.getItem(0)).thenReturn(mockNoIconMenu);
+        when(menu.getItem(1)).thenReturn(mockNoIconMenu2);
+
+        // Execute the test method
+        EditText et = mActivity.findViewById(R.id.editText);
+        Editor editor = et.getEditorForTesting();
+        editor.adjustIconSpacing(menu);
+
+        // Verify
+        verify(mockNoIconMenu, times(0)).setIcon(any());
+        verify(mockNoIconMenu2, times(0)).setIcon(any());
+    }
 }
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 9ccf3b3..3b8b8c7 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -117,6 +117,31 @@
     }
 
     @Test
+    public void testGetReportedHdrTypes_returns_mode_specific_hdr_types() {
+        setDisplayInfoPortrait(mDisplayInfo);
+        float[] alternativeRefreshRates = new float[0];
+        int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+        Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithDv);
+
+        int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+        Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithoutDv);
+
+        mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+        mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+        final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+        mDisplayInfo.modeId = 0;
+        assertArrayEquals(hdrTypesWithDv, display.getReportedHdrTypes());
+
+        mDisplayInfo.modeId = 1;
+        assertArrayEquals(hdrTypesWithoutDv, display.getReportedHdrTypes());
+    }
+
+    @Test
     public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
         setDisplayInfoPortrait(mDisplayInfo);
         final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 04bff97..306d619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -32,6 +32,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 
@@ -115,7 +116,7 @@
             boolean latest) {
         for (int i = mPending.size() - 1; i >= 0; --i) {
             if (mPending.get(i).mTaskView != taskView) continue;
-            if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
+            if (TransitionUtil.isClosingType(mPending.get(i).mType) == closing) {
                 return mPending.get(i);
             }
             if (latest) {
@@ -148,7 +149,7 @@
         final TaskViewTaskController taskView = findTaskView(triggerTask);
         if (taskView == null) return null;
         // Opening types should all be initiated by shell
-        if (!Transitions.isClosingType(request.getType())) return null;
+        if (!TransitionUtil.isClosingType(request.getType())) return null;
         PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
         if (pending == null) {
             pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
@@ -238,7 +239,7 @@
         for (int i = 0; i < info.getChanges().size(); ++i) {
             final TransitionInfo.Change chg = info.getChanges().get(i);
             if (chg.getTaskInfo() == null) continue;
-            if (Transitions.isClosingType(chg.getMode())) {
+            if (TransitionUtil.isClosingType(chg.getMode())) {
                 final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
                 TaskViewTaskController tv = findTaskView(chg.getTaskInfo());
                 if (tv == null) {
@@ -255,7 +256,7 @@
                     tv.prepareCloseAnimation();
                 }
                 changesHandled++;
-            } else if (Transitions.isOpeningType(chg.getMode())) {
+            } else if (TransitionUtil.isOpeningType(chg.getMode())) {
                 final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
                 final TaskViewTaskController tv;
                 if (taskIsNew) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 579f7aa..c767376 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -29,7 +29,7 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
 /**
  * Wrapper to handle the ActivityEmbedding animation update in one
@@ -90,7 +90,7 @@
         mChange = change;
         mLeash = leash;
         mWholeAnimationBounds.set(wholeAnimationBounds);
-        if (Transitions.isClosingType(change.getMode())) {
+        if (TransitionUtil.isClosingType(change.getMode())) {
             // When it is closing, we want to show the content at the start position in case the
             // window is resizing as well. For example, when the activities is changing from split
             // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index fe3c4ea..1df6ecd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -43,7 +43,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -184,7 +184,7 @@
         if (isChangeTransition) {
             return createChangeAnimationAdapters(info, startTransaction);
         }
-        if (Transitions.isClosingType(info.getType())) {
+        if (TransitionUtil.isClosingType(info.getType())) {
             return createCloseAnimationAdapters(info);
         }
         return createOpenAnimationAdapters(info);
@@ -219,7 +219,7 @@
         final Rect openingWholeScreenBounds = new Rect();
         final Rect closingWholeScreenBounds = new Rect();
         for (TransitionInfo.Change change : info.getChanges()) {
-            if (Transitions.isOpeningType(change.getMode())) {
+            if (TransitionUtil.isOpeningType(change.getMode())) {
                 openingChanges.add(change);
                 openingWholeScreenBounds.union(change.getEndAbsBounds());
             } else {
@@ -271,7 +271,7 @@
                 continue;
             }
             final TransitionInfo.Change change = adapter.mChange;
-            if (Transitions.isOpeningType(adapter.mChange.getMode())) {
+            if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
                 // Need to screenshot after startTransaction is applied otherwise activity
                 // may not be visible or ready yet.
                 postStartTransactionCallbacks.add(
@@ -343,7 +343,7 @@
                 // When the parent window is also included in the transition as an opening window,
                 // we would like to animate the parent window instead.
                 final TransitionInfo.Change parentChange = info.getChange(parentToken);
-                if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+                if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) {
                     // We won't create a separate animation for the parent, but to animate the
                     // parent for the child resizing.
                     handledChanges.add(parentChange);
@@ -404,7 +404,7 @@
                 // No-op if it will be covered by the changing parent window, or it is a changing
                 // window without bounds change.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
-            } else if (Transitions.isClosingType(change.getMode())) {
+            } else if (TransitionUtil.isClosingType(change.getMode())) {
                 animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
                 shouldShouldBackgroundColor = false;
             } else {
@@ -469,7 +469,7 @@
                 // When the parent window is also included in the transition as an opening window,
                 // we would like to animate the parent window instead.
                 final TransitionInfo.Change parentChange = info.getChange(parentToken);
-                if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+                if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) {
                     changingChanges.add(parentChange);
                 }
             }
@@ -491,8 +491,8 @@
                 // No-op if it will be covered by the changing parent window.
                 continue;
             }
-            hasOpeningWindow |= Transitions.isOpeningType(change.getMode());
-            hasClosingWindow |= Transitions.isClosingType(change.getMode());
+            hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
+            hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
         }
         return hasOpeningWindow && hasClosingWindow;
     }
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 d10a674..cb8342a 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
@@ -36,7 +36,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
 /** Animation spec for ActivityEmbedding transition. */
 // TODO(b/206557124): provide an easier way to customize animation
@@ -73,7 +73,7 @@
     @NonNull
     static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) {
         // Noop but just keep the window showing/hiding.
-        final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f;
+        final float alpha = TransitionUtil.isClosingType(change.getMode()) ? 0f : 1f;
         return new AlphaAnimation(alpha, alpha);
     }
 
@@ -198,7 +198,7 @@
     @NonNull
     Animation loadOpenAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
-        final boolean isEnter = Transitions.isOpeningType(change.getMode());
+        final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
         final Animation animation;
         if (shouldShowBackdrop(info, change)) {
             animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
@@ -222,7 +222,7 @@
     @NonNull
     Animation loadCloseAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
-        final boolean isEnter = Transitions.isOpeningType(change.getMode());
+        final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
         final Animation animation;
         if (shouldShowBackdrop(info, change)) {
             animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
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 e5c0570..6b0337d 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
@@ -40,7 +40,6 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
 
 import android.animation.Animator;
 import android.app.ActivityManager;
@@ -72,6 +71,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.CounterRotatorHelper;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.Optional;
 
@@ -702,7 +702,7 @@
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (change == enterPip) continue;
-            if (isOpeningType(change.getMode())) {
+            if (TransitionUtil.isOpeningType(change.getMode())) {
                 final SurfaceControl leash = change.getLeash();
                 startTransaction.show(leash).setAlpha(leash, 1.f);
             }
@@ -873,7 +873,7 @@
                 continue;
             }
 
-            if (isOpeningType(mode) && change.getParent() == null) {
+            if (TransitionUtil.isOpeningType(mode) && change.getParent() == null) {
                 final SurfaceControl leash = change.getLeash();
                 final Rect endBounds = change.getEndAbsBounds();
                 startTransaction
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 520da92..c96323a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.OneShotRemoteHandler;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 
@@ -531,7 +532,7 @@
     }
 
     private boolean isOpeningTransition(TransitionInfo info) {
-        return Transitions.isOpeningType(info.getType())
+        return TransitionUtil.isOpeningType(info.getType())
                 || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE
                 || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
     }
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 7833cfe..8b24d86 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
@@ -61,8 +61,8 @@
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.transition.Transitions.isClosingType;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import static com.android.wm.shell.util.TransitionUtil.isClosingType;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -135,6 +135,7 @@
 import com.android.wm.shell.transition.LegacyTransitions;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -2271,7 +2272,7 @@
 
             // Use normal animations.
             return false;
-        } else if (mMixedHandler != null && Transitions.hasDisplayChange(info)) {
+        } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
             // A display-change has been un-expectedly inserted into the transition. Redirect
             // handling to the mixed-handler to deal with splitting it up.
             if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 19133e2..628ce27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -28,6 +28,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.wm.shell.util.CounterRotator;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.List;
 
@@ -57,7 +58,7 @@
         for (int i = numChanges - 1; i >= 0; --i) {
             final TransitionInfo.Change change = changes.get(i);
             final WindowContainerToken parent = change.getParent();
-            if (!Transitions.isClosingType(change.getMode())
+            if (!TransitionUtil.isClosingType(change.getMode())
                     || !TransitionInfo.isIndependent(change, info) || parent == null) {
                 continue;
             }
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 8e916e6..ef405c8 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
@@ -44,6 +44,7 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 import java.util.Optional;
@@ -149,7 +150,7 @@
             mSplitHandler.addEnterOrExitIfNeeded(request, out);
             return out;
         } else if (request.getRemoteTransition() != null
-                && Transitions.isOpeningType(request.getType())
+                && TransitionUtil.isOpeningType(request.getType())
                 && (request.getTriggerTask() == null
                 || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME
                         && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) {
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 a3e05f2..f66c26b 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
@@ -112,6 +112,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -445,7 +446,7 @@
                         backgroundColorForTransition);
 
                 if (!isTask && a.hasExtension()) {
-                    if (!Transitions.isOpeningType(change.getMode())) {
+                    if (!TransitionUtil.isOpeningType(change.getMode())) {
                         // Can screenshot now (before startTransaction is applied)
                         edgeExtendWindow(change, a, startTransaction, finishTransaction);
                     } else {
@@ -456,7 +457,7 @@
                     }
                 }
 
-                final Rect clipRect = Transitions.isClosingType(change.getMode())
+                final Rect clipRect = TransitionUtil.isClosingType(change.getMode())
                         ? new Rect(mRotator.getEndBoundsInStartRotation(change))
                         : new Rect(change.getEndAbsBounds());
                 clipRect.offsetTo(0, 0);
@@ -562,12 +563,12 @@
         final int flags = info.getFlags();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
-        final boolean isOpeningType = Transitions.isOpeningType(type);
-        final boolean enter = Transitions.isOpeningType(changeMode);
+        final boolean isOpeningType = TransitionUtil.isOpeningType(type);
+        final boolean enter = TransitionUtil.isOpeningType(changeMode);
         final boolean isTask = change.getTaskInfo() != null;
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
-        final Rect endBounds = Transitions.isClosingType(changeMode)
+        final Rect endBounds = TransitionUtil.isClosingType(changeMode)
                 ? mRotator.getEndBoundsInStartRotation(change)
                 : change.getEndAbsBounds();
 
@@ -689,8 +690,8 @@
     private void attachThumbnail(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, TransitionInfo.Change change,
             TransitionInfo.AnimationOptions options, float cornerRadius) {
-        final boolean isOpen = Transitions.isOpeningType(change.getMode());
-        final boolean isClose = Transitions.isClosingType(change.getMode());
+        final boolean isOpen = TransitionUtil.isOpeningType(change.getMode());
+        final boolean isClose = TransitionUtil.isClosingType(change.getMode());
         if (isOpen) {
             if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
                 attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
@@ -772,16 +773,16 @@
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
-                if (Transitions.isOpeningType(change.getMode())) {
+                if (TransitionUtil.isOpeningType(change.getMode())) {
                     hasOpenWallpaper = true;
-                } else if (Transitions.isClosingType(change.getMode())) {
+                } else if (TransitionUtil.isClosingType(change.getMode())) {
                     hasCloseWallpaper = true;
                 }
             }
         }
 
         if (hasOpenWallpaper && hasCloseWallpaper) {
-            return Transitions.isOpeningType(info.getType())
+            return TransitionUtil.isOpeningType(info.getType())
                     ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
         } else if (hasOpenWallpaper) {
             return WALLPAPER_TRANSITION_OPEN;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 02f19eb..3c4e889 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -39,6 +39,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 
@@ -93,7 +94,7 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (!Transitions.SHELL_TRANSITIONS_ROTATION && Transitions.hasDisplayChange(info)) {
+        if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) {
             // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
             // operations of the start transaction may be ignored.
             return false;
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 5a5ceab..8d29901 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
@@ -54,6 +54,7 @@
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.util.TransitionUtil;
 
 /** The helper class that provides methods for adding styles to transition animations. */
 public class TransitionAnimationHelper {
@@ -66,7 +67,7 @@
         final int type = info.getType();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
-        final boolean enter = Transitions.isOpeningType(changeMode);
+        final boolean enter = TransitionUtil.isOpeningType(changeMode);
         final boolean isTask = change.getTaskInfo() != null;
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
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 0a67477..3b154d1 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,13 +19,11 @@
 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_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
-import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
@@ -33,6 +31,8 @@
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
+import static com.android.wm.shell.util.TransitionUtil.isClosingType;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -319,29 +319,6 @@
         }
     }
 
-    /** @return true if the transition was triggered by opening something vs closing something */
-    public static boolean isOpeningType(@WindowManager.TransitionType int type) {
-        return type == TRANSIT_OPEN
-                || type == TRANSIT_TO_FRONT
-                || type == TRANSIT_KEYGUARD_GOING_AWAY;
-    }
-
-    /** @return true if the transition was triggered by closing something vs opening something */
-    public static boolean isClosingType(@WindowManager.TransitionType int type) {
-        return type == TRANSIT_CLOSE || type == 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) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-            if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
      */
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
new file mode 100644
index 0000000..145f759
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -0,0 +1,57 @@
+/*
+ * 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 static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+
+import android.annotation.NonNull;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+
+/** Various utility functions for transitions. */
+public class TransitionUtil {
+
+    /** @return true if the transition was triggered by opening something vs closing something */
+    public static boolean isOpeningType(@WindowManager.TransitionType int type) {
+        return type == TRANSIT_OPEN
+                || type == TRANSIT_TO_FRONT
+                || type == TRANSIT_KEYGUARD_GOING_AWAY;
+    }
+
+    /** @return true if the transition was triggered by closing something vs opening something */
+    public static boolean isClosingType(@WindowManager.TransitionType int type) {
+        return type == TRANSIT_CLOSE || type == 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) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5136675..4da4c7f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,9 +16,9 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index b6ab262..7faa13c 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -16,9 +16,9 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.CallbackExecutor;
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 0b08681..faa7f7f 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -3049,7 +3049,13 @@
 
 
         /**
-         * Gets the {@link LogSessionId}.
+         * Sets the {@link LogSessionId}.
+         *
+         * <p>The implementation of this method varies by DRM provider; Please refer
+         * to your DRM provider documentation for more details on this method.
+         *
+         * @throws UnsupportedOperationException when the vendor plugin does not
+         * implement this method
          */
         public void setLogSessionId(@NonNull LogSessionId logSessionId) {
             Objects.requireNonNull(logSessionId);
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 38115e1..3f44b09 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -16,9 +16,9 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.NonNull;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
index 76543f4..6089f42 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -17,10 +17,10 @@
 package com.android.mediaframeworktest.unit;
 
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.FX_KEY_CLICK;
 
 import static org.mockito.ArgumentMatchers.anyInt;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
index 9c813c2..71228e2 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
@@ -16,10 +16,10 @@
 
 package com.android.mediaframeworktest.unit;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import static org.junit.Assert.assertEquals;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
index ffed39a..ac24a10 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
@@ -16,10 +16,10 @@
 
 package com.android.mediaframeworktest.unit;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import static org.junit.Assert.assertEquals;
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index b36cb5c..dfc8aa0 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -41,6 +41,15 @@
         android:excludeFromRecents="true"
         android:theme="@style/Theme.CredentialSelector">
     </activity>
+
+    <receiver
+        android:name=".CredentialProviderReceiver"
+        android:exported="true"
+        android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
+        <intent-filter>
+            <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
+        </intent-filter>
+    </receiver>
   </application>
 
 </manifest>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 3eb58f1..ee51242 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -94,6 +94,8 @@
   <string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
   <!-- Spoken content description of the close "X" icon button. -->
   <string name="accessibility_close_button">Close</string>
+  <!-- Spoken content description of the close "X" icon button. [CHAR LIMIT=NONE] -->
+  <string name="accessibility_snackbar_dismiss">Dismiss</string>
 
   <!-- Strings for the get flow. -->
   <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
index a58a038..c7e4796 100644
--- a/packages/CredentialManager/res/values/themes.xml
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-
   <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
-    <item name="android:statusBarColor">@android:color/transparent</item>
     <item name="android:windowContentOverlay">@null</item>
     <item name="android:windowNoTitle">true</item>
     <item name="android:windowBackground">@android:color/transparent</item>
     <item name="android:windowIsTranslucent">true</item>
-    <item name="android:colorBackgroundCacheHint">@null</item>
-    <item name="fontFamily">google-sans</item>
+    <item name="android:statusBarColor">@android:color/transparent</item>
+    <item name="android:navigationBarColor">@android:color/transparent</item>
   </style>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e3236b3..89ce451 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,8 +16,6 @@
 
 package com.android.credentialmanager
 
-import android.app.slice.Slice
-import android.app.slice.SliceSpec
 import android.content.Context
 import android.content.Intent
 import android.credentials.CreateCredentialRequest
@@ -35,7 +33,6 @@
 import android.credentials.ui.BaseDialogResult
 import android.credentials.ui.ProviderPendingIntentResponse
 import android.credentials.ui.UserSelectionDialogResult
-import android.net.Uri
 import android.os.IBinder
 import android.os.Binder
 import android.os.Bundle
@@ -73,7 +70,7 @@
         requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
-        ) ?: testCreatePasskeyRequestInfo()
+        ) ?: testCreatePasswordRequestInfo()
 
         providerEnabledList = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE ->
@@ -230,7 +227,8 @@
                             context,
                             "key1", "subkey-1", "elisa.beckett@gmail.com",
                             20, 7, 27, Instant.ofEpochSecond(10L),
-                            "Legal note"
+                            "You can use your passkey on this or other devices. It is saved to " +
+                                "the Password Manager for elisa.beckett@gmail.com."
                         ),
                         CreateTestUtils.newCreateEntry(
                             context,
@@ -239,11 +237,9 @@
                             null
                         ),
                     )
-                )
-                .setRemoteEntry(
-                    newRemoteEntry("key2", "subkey-1")
-                )
-                .build(),
+                ).setRemoteEntry(
+                    CreateTestUtils.newRemoteCreateEntry(context, "key2", "subkey-1")
+                ).build(),
             CreateCredentialProviderData
                 .Builder("com.dashlane")
                 .setSaveEntries(
@@ -258,11 +254,11 @@
                             context,
                             "key1", "subkey-4", "elisa.work@dashlane.com",
                             20, 7, 27, Instant.ofEpochSecond(14L),
-                            null
+                            "You can use your passkey on this or other devices. It is saved to " +
+                                "the Password Manager for elisa.work@dashlane.com"
                         ),
                     )
-                )
-                .build(),
+                ).build(),
         )
     }
 
@@ -318,7 +314,7 @@
                         ),
                     )
                 ).setRemoteEntry(
-                    newRemoteEntry("key4", "subkey-1")
+                    GetTestUtils.newRemoteCredentialEntry(context, "key4", "subkey-1")
                 ).build(),
             GetCredentialProviderData.Builder("com.dashlane")
                 .setCredentialEntries(
@@ -333,10 +329,12 @@
                         ),
                     )
                 ).setAuthenticationEntries(
-                     listOf(GetTestUtils.newAuthenticationEntry(
-                         context, "key2", "subkey-1", "foo@email.com",
-                         AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
-                     ))
+                     listOf(
+                         GetTestUtils.newAuthenticationEntry(
+                             context, "key2", "subkey-1", "foo@email.com",
+                             AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
+                         )
+                     )
                 ).setActionChips(
                     listOf(
                         GetTestUtils.newActionEntry(
@@ -348,20 +346,6 @@
         )
     }
 
-
-    private fun newRemoteEntry(
-        key: String,
-        subkey: String,
-    ): Entry {
-        return Entry(
-            key,
-            subkey,
-            Slice.Builder(
-                Uri.EMPTY, SliceSpec("type", 1)
-            ).build()
-        )
-    }
-
     private fun testCreatePasskeyRequestInfo(): RequestInfo {
         val request = CreatePublicKeyCredentialRequest(
             "{\"extensions\": {\n" +
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
new file mode 100644
index 0000000..ee8cffe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.credentialmanager.common.Constants
+
+
+class CredentialProviderReceiver : BroadcastReceiver() {
+
+    override fun onReceive(context: Context?, intent: Intent?) {
+        Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
+
+        val sharedPreferences = context?.getSharedPreferences(context?.packageName,
+                Context.MODE_PRIVATE)
+        sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index d618e74..a3e4c81 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -39,7 +39,7 @@
 import com.android.credentialmanager.createflow.hasContentToDisplay
 import com.android.credentialmanager.getflow.GetCredentialScreen
 import com.android.credentialmanager.getflow.hasContentToDisplay
-import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
+import com.android.credentialmanager.ui.theme.PlatformTheme
 
 @ExperimentalMaterialApi
 class CredentialSelectorActivity : ComponentActivity() {
@@ -50,7 +50,7 @@
             val userConfigRepo = UserConfigRepo(this)
             val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
             setContent {
-                CredentialSelectorTheme {
+                PlatformTheme {
                     CredentialManagerBottomSheet(
                         credManRepo,
                         userConfigRepo
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index aa0959c..ccfc132 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -287,6 +287,7 @@
                     pendingIntent = structuredAuthEntry.pendingIntent,
                     fillInIntent = entry.frameworkExtrasIntent,
                     title = title,
+                    providerDisplayName = providerDisplayName,
                     icon = providerIcon,
                     isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED,
                     isLastUnlocked =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
index eb3d188..9216429 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -29,6 +29,8 @@
 import androidx.credentials.provider.CreateEntry
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry
+import androidx.credentials.provider.RemoteCreateEntry
+import androidx.credentials.provider.RemoteCredentialEntry
 
 import java.time.Instant
 
@@ -69,6 +71,21 @@
             )
         }
 
+        internal fun newRemoteCredentialEntry(
+            context: Context,
+            key: String,
+            subkey: String,
+        ): Entry {
+            val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
+            val pendingIntent =
+                PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+            return Entry(
+                key,
+                subkey,
+                RemoteCredentialEntry(pendingIntent).slice
+            )
+        }
+
         internal fun newActionEntry(
             context: Context,
             key: String,
@@ -203,5 +220,20 @@
                 Intent()
             )
         }
+
+        internal fun newRemoteCreateEntry(
+            context: Context,
+            key: String,
+            subkey: String,
+        ): Entry {
+            val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
+            val pendingIntent =
+                PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+            return Entry(
+                key,
+                subkey,
+                RemoteCreateEntry(pendingIntent).slice
+            )
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index 58edb25..335d58a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -451,7 +451,7 @@
 }
 
 @Composable
-private fun Scrim(
+internal fun Scrim(
     color: Color,
     onDismiss: () -> Unit,
     visible: Boolean
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
index 984057a..b94840f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.common.ui
 
+import androidx.compose.foundation.layout.padding
 import com.android.credentialmanager.R
 import androidx.compose.material.Icon
 import androidx.compose.material.IconButton
@@ -24,7 +25,6 @@
 import androidx.compose.material.icons.outlined.VisibilityOff
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
@@ -32,17 +32,22 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun ActionButton(text: String, onClick: () -> Unit) {
     TextButton(
+        modifier = Modifier.padding(vertical = 4.dp),
         onClick = onClick,
         colors = ButtonDefaults.textButtonColors(
             contentColor = MaterialTheme.colorScheme.primary,
         )
     ) {
-        Text(text = text)
+        LargeLabelText(
+            text = text,
+            modifier = Modifier.padding(vertical = 10.dp, horizontal = 12.dp),
+        )
     }
 }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index 85e5c1e..a622e07 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -16,33 +16,73 @@
 
 package com.android.credentialmanager.common.ui
 
-import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
 
 /**
- * By default the card is filled with surfaceVariant color. This container card instead fills the
- * background color with surface corlor.
+ * Container card for the whole sheet.
+ *
+ * Surface 1 color. No vertical padding. 24dp horizontal padding. 24dp bottom padding. 24dp top
+ * padding if [topAppBar] is not present, and none otherwise.
  */
 @Composable
-fun ContainerCard(
+fun SheetContainerCard(
+    topAppBar: (@Composable () -> Unit)? = null,
     modifier: Modifier = Modifier,
-    shape: Shape = CardDefaults.shape,
-    border: BorderStroke? = null,
     content: @Composable ColumnScope.() -> Unit,
 ) {
     Card(
-        modifier = modifier.fillMaxWidth(),
-        shape = shape,
-        border = border,
+        modifier = modifier.fillMaxWidth().wrapContentHeight(),
+        border = null,
         colors = CardDefaults.cardColors(
-            containerColor = MaterialTheme.colorScheme.surface,
+            containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
+                ElevationTokens.Level1
+            ),
+        ),
+    ) {
+        if (topAppBar != null) {
+            topAppBar()
+        }
+        Column(
+            modifier = Modifier.padding(
+                start = 24.dp,
+                end = 24.dp,
+                bottom = 18.dp,
+                top = if (topAppBar == null) 24.dp else 0.dp
+            ).fillMaxWidth().wrapContentHeight(),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            content = content,
+        )
+    }
+}
+
+/**
+ * Container card for the entries.
+ *
+ * Surface 3 color. No padding. Four rounded corner shape.
+ */
+@Composable
+fun CredentialContainerCard(
+    modifier: Modifier = Modifier,
+    content: @Composable ColumnScope.() -> Unit,
+) {
+    Card(
+        modifier = modifier.fillMaxWidth().wrapContentHeight(),
+        shape = MaterialTheme.shapes.medium,
+        border = null,
+        colors = CardDefaults.cardColors(
+            containerColor = Color.Transparent,
         ),
         content = content,
     )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt
new file mode 100644
index 0000000..b2489fd
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.material3.ColorScheme
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlin.math.ln
+
+fun ColorScheme.surfaceColorAtElevation(elevation: Dp): Color {
+    if (elevation == 0.dp) return surface
+    val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f
+    return surfaceTint.copy(alpha = alpha).compositeOver(surface)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt
new file mode 100644
index 0000000..23ad53c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun EntryListColumn(content: LazyListScope.() -> Unit) {
+    LazyColumn(
+        verticalArrangement = Arrangement.spacedBy(2.dp),
+        content = content,
+    )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
index d8ee750..8f48f6b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
@@ -16,21 +16,28 @@
 
 package com.android.credentialmanager.common.ui
 
+import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.FilledTonalButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
 
+/**  Primary container color; label-large button text; on-primary button text color. */
 @Composable
 fun ConfirmButton(text: String, onClick: () -> Unit) {
     FilledTonalButton(
+        modifier = Modifier.padding(vertical = 4.dp),
         onClick = onClick,
         colors = ButtonDefaults.filledTonalButtonColors(
-            containerColor = MaterialTheme.colorScheme.primaryContainer,
-            contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+            containerColor = MaterialTheme.colorScheme.primary,
+            contentColor = MaterialTheme.colorScheme.onPrimary,
         )
     ) {
-        Text(text = text)
+        LargeLabelText(
+            text = text,
+            modifier = Modifier.padding(vertical = 10.dp, horizontal = 24.dp),
+        )
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt
new file mode 100644
index 0000000..e1e666e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.ui.unit.dp
+
+/** Copied from androidx.compose.material3.tokens. */
+internal object ElevationTokens {
+    val Level0 = 0.0.dp
+    val Level1 = 1.0.dp
+    val Level2 = 3.0.dp
+    val Level3 = 6.0.dp
+    val Level4 = 8.0.dp
+    val Level5 = 12.0.dp
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index aefd534..0eaaf97 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -16,55 +16,314 @@
 
 package com.android.credentialmanager.common.ui
 
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.outlined.Lock
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.SuggestionChip
 import androidx.compose.material3.SuggestionChipDefaults
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.R
 import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.Shapes
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun Entry(
-    onClick: () -> Unit,
-    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
-    icon: @Composable (() -> Unit)? = null,
+    onClick: () -> Unit,
+    entryHeadlineText: String,
+    entrySecondLineText: String? = null,
+    entryThirdLineText: String? = null,
+    /** Supply one and only one of the [iconImageBitmap], [iconImageVector], or [iconPainter] for
+     *  drawing the leading icon. */
+    iconImageBitmap: ImageBitmap? = null,
+    shouldApplyIconImageBitmapTint: Boolean = false,
+    iconImageVector: ImageVector? = null,
+    iconPainter: Painter? = null,
+    /** This will replace the [entrySecondLineText] value and render the text along with a
+     *  mask on / off toggle for hiding / displaying the password value. */
+    passwordValue: String? = null,
+    /** If true, draws a trailing lock icon. */
+    isLockedAuthEntry: Boolean = false,
 ) {
     SuggestionChip(
-        modifier = modifier.fillMaxWidth(),
+        modifier = modifier.fillMaxWidth().wrapContentHeight(),
         onClick = onClick,
         shape = EntryShape.FullSmallRoundedCorner,
-        label = label,
-        icon = icon,
+        label = {
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                modifier = Modifier.fillMaxWidth().padding(all = 16.dp),
+                verticalAlignment = Alignment.CenterVertically,
+            ) {
+                Column(modifier = Modifier.wrapContentSize()) {
+                    SmallTitleText(entryHeadlineText)
+                    if (passwordValue != null) {
+                        Row(modifier = Modifier.fillMaxWidth()) {
+                            val visualTransformation = remember { PasswordVisualTransformation() }
+                            val originalPassword by remember {
+                                mutableStateOf(passwordValue)
+                            }
+                            val displayedPassword = remember {
+                                mutableStateOf(
+                                    visualTransformation.filter(
+                                        AnnotatedString(originalPassword)
+                                    ).text.text
+                                )
+                            }
+                            BodySmallText(displayedPassword.value)
+                            ToggleVisibilityButton(
+                                modifier = Modifier.padding(start = 5.dp).size(24.dp),
+                                onToggle = {
+                                    if (it) {
+                                        displayedPassword.value = originalPassword
+                                    } else {
+                                        displayedPassword.value = visualTransformation.filter(
+                                            AnnotatedString(originalPassword)
+                                        ).text.text
+                                    }
+                                },
+                            )
+                        }
+                    } else if (entrySecondLineText != null) {
+                        BodySmallText(entrySecondLineText)
+                    }
+                    if (entryThirdLineText != null) {
+                        BodySmallText(entryThirdLineText)
+                    }
+                }
+                if (isLockedAuthEntry) {
+                    Box(modifier = Modifier.wrapContentSize().padding(end = 16.dp)) {
+                        Icon(
+                            imageVector = Icons.Outlined.Lock,
+                            // Decorative purpose only.
+                            contentDescription = null,
+                            modifier = Modifier.size(24.dp),
+                            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                        )
+                    }
+                }
+            }
+        },
+        icon =
+        if (iconImageBitmap != null) {
+            if (shouldApplyIconImageBitmapTint) {
+                {
+                    Box(modifier = Modifier.wrapContentSize()
+                        .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+                        Icon(
+                            modifier = Modifier.size(24.dp),
+                            bitmap = iconImageBitmap,
+                            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                            // Decorative purpose only.
+                            contentDescription = null,
+                        )
+                    }
+                }
+            } else {
+                {
+                    Box(modifier = Modifier.wrapContentSize()
+                        .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+                        Image(
+                            modifier = Modifier.size(24.dp),
+                            bitmap = iconImageBitmap,
+                            // Decorative purpose only.
+                            contentDescription = null,
+                        )
+                    }
+                }
+            }
+        } else if (iconImageVector != null) {
+            {
+                Box(modifier = Modifier.wrapContentSize()
+                    .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+                    Icon(
+                        modifier = Modifier.size(24.dp),
+                        imageVector = iconImageVector,
+                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                        // Decorative purpose only.
+                        contentDescription = null,
+                    )
+                }
+            }
+        } else if (iconPainter != null) {
+            {
+                Box(modifier = Modifier.wrapContentSize()
+                    .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+                    Icon(
+                        modifier = Modifier.size(24.dp),
+                        painter = iconPainter,
+                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                        // Decorative purpose only.
+                        contentDescription = null,
+                    )
+                }
+            }
+        } else {
+            null
+        },
         border = null,
         colors = SuggestionChipDefaults.suggestionChipColors(
-            containerColor = MaterialTheme.colorScheme.surfaceVariant,
+            containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
+                ElevationTokens.Level3
+            ),
+            // TODO: remove?
             labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
             iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
         ),
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
+/**
+ * A variation of the normal entry in that its background is transparent and the paddings are
+ * different (no horizontal padding).
+ */
 @Composable
-fun TransparentBackgroundEntry(
+fun ActionEntry(
     onClick: () -> Unit,
-    label: @Composable () -> Unit,
-    modifier: Modifier = Modifier,
-    icon: @Composable (() -> Unit)? = null,
+    entryHeadlineText: String,
+    entrySecondLineText: String? = null,
+    iconImageBitmap: ImageBitmap,
 ) {
     SuggestionChip(
-        modifier = modifier.fillMaxWidth(),
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
         onClick = onClick,
-        label = label,
-        icon = icon,
+        shape = Shapes.large,
+        label = {
+            Column(modifier = Modifier.wrapContentSize()
+                .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+                SmallTitleText(entryHeadlineText)
+                if (entrySecondLineText != null) {
+                    BodySmallText(entrySecondLineText)
+                }
+            }
+        },
+        icon = {
+            Box(modifier = Modifier.wrapContentSize().padding(vertical = 16.dp)) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    bitmap = iconImageBitmap,
+                    // Decorative purpose only.
+                    contentDescription = null,
+                )
+            }
+        },
         border = null,
         colors = SuggestionChipDefaults.suggestionChipColors(
             containerColor = Color.Transparent,
         ),
     )
+}
+
+/**
+ * A single row of leading icon and text describing a benefit of passkeys, used by the
+ * [com.android.credentialmanager.createflow.PasskeyIntroCard].
+ */
+@Composable
+fun PasskeyBenefitRow(
+    leadingIconPainter: Painter,
+    text: String,
+) {
+    Row(
+        horizontalArrangement = Arrangement.spacedBy(16.dp),
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = Modifier.fillMaxWidth()
+    ) {
+        Icon(
+            modifier = Modifier.size(24.dp),
+            painter = leadingIconPainter,
+            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+            // Decorative purpose only.
+            contentDescription = null,
+        )
+        BodyMediumText(text = text)
+    }
+}
+
+/**
+ * A single row of one or two CTA buttons for continuing or cancelling the current step.
+ */
+@Composable
+fun CtaButtonRow(
+    leftButton: (@Composable () -> Unit)? = null,
+    rightButton: (@Composable () -> Unit)? = null,
+) {
+    Row(
+        horizontalArrangement =
+        if (leftButton == null) Arrangement.End
+        else if (rightButton == null) Arrangement.Start
+        else Arrangement.SpaceBetween,
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = Modifier.fillMaxWidth()
+    ) {
+        if (leftButton != null) {
+            leftButton()
+        }
+        if (rightButton != null) {
+            rightButton()
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionTopAppBar(
+    text: String,
+    onNavigationIconClicked: () -> Unit,
+) {
+    TopAppBar(
+        title = {
+            LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
+        },
+        navigationIcon = {
+            IconButton(
+                modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp),
+                onClick = onNavigationIconClicked
+            ) {
+                Box(
+                    modifier = Modifier.size(48.dp),
+                    contentAlignment = Alignment.Center,
+                ) {
+                    Icon(
+                        imageVector = Icons.Filled.ArrowBack,
+                        contentDescription = stringResource(
+                            R.string.accessibility_back_arrow_button
+                        ),
+                        modifier = Modifier.size(16.dp),
+                        tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                    )
+                }
+            }
+        },
+        colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
+        modifier = Modifier.padding(top = 12.dp, bottom = 8.dp)
+    )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt
new file mode 100644
index 0000000..ac79844
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+
+/** Tinted primary; centered; 32X32. */
+@Composable
+fun HeadlineIcon(bitmap: ImageBitmap, tint: Color? = null) {
+    Row(
+        horizontalArrangement = Arrangement.Center,
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+    ) {
+        Icon(
+            modifier = Modifier.size(32.dp),
+            bitmap = bitmap,
+            tint = tint ?: MaterialTheme.colorScheme.primary,
+            // Decorative purpose only.
+            contentDescription = null,
+        )
+    }
+}
+
+@Composable
+fun HeadlineIcon(imageVector: ImageVector) {
+    Row(
+        horizontalArrangement = Arrangement.Center,
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+    ) {
+        Icon(
+            modifier = Modifier.size(32.dp),
+            imageVector = imageVector,
+            tint = MaterialTheme.colorScheme.primary,
+            // Decorative purpose only.
+            contentDescription = null,
+        )
+    }
+}
+
+@Composable
+fun HeadlineIcon(painter: Painter) {
+    Row(
+        horizontalArrangement = Arrangement.Center,
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+    ) {
+        Icon(
+            modifier = Modifier.size(32.dp),
+            painter = painter,
+            tint = MaterialTheme.colorScheme.primary,
+            // Decorative purpose only.
+            contentDescription = null,
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
new file mode 100644
index 0000000..c63771e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.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.credentialmanager.common.ui
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun CredentialListSectionHeader(text: String) {
+    InternalSectionHeader(text, MaterialTheme.colorScheme.onSurfaceVariant)
+}
+
+@Composable
+fun MoreAboutPasskeySectionHeader(text: String) {
+    InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface)
+}
+
+@Composable
+private fun InternalSectionHeader(text: String, color: Color) {
+    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+        SectionHeaderText(
+            text,
+            modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
+            color = color
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
new file mode 100644
index 0000000..8061da7
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.Scrim
+import com.android.credentialmanager.ui.theme.Shapes
+
+@Composable
+fun Snackbar(
+    contentText: String,
+    action: (@Composable () -> Unit)? = null,
+    onDismiss: () -> Unit,
+) {
+    BoxWithConstraints {
+        Box(Modifier.fillMaxSize()) {
+            Scrim(
+                color = Color.Transparent,
+                onDismiss = onDismiss,
+                visible = true
+            )
+        }
+        Box(
+            modifier = Modifier
+                .align(Alignment.BottomCenter).wrapContentSize().padding(bottom = 18.dp)
+        ) {
+            Card(
+                shape = Shapes.medium,
+                modifier = Modifier.wrapContentSize(),
+                colors = CardDefaults.cardColors(
+                    containerColor = MaterialTheme.colorScheme.inverseSurface,
+                )
+            ) {
+                Row(
+                    modifier = Modifier.wrapContentSize(),
+                    verticalAlignment = Alignment.CenterVertically,
+                ) {
+                    SnackbarContentText(contentText, modifier = Modifier.padding(
+                        top = 18.dp, bottom = 18.dp, start = 24.dp,
+                    ))
+                    if (action != null) {
+                        action()
+                    }
+                    IconButton(onClick = onDismiss, modifier = Modifier.padding(
+                        top = 18.dp, bottom = 18.dp, start = 16.dp, end = 24.dp,
+                    )) {
+                        Icon(
+                            Icons.Filled.Close,
+                            contentDescription = stringResource(
+                                R.string.accessibility_snackbar_dismiss
+                            ),
+                            tint = MaterialTheme.colorScheme.inverseOnSurface,
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 3a66dda..8f7c37e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -16,69 +16,144 @@
 
 package com.android.credentialmanager.common.ui
 
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextAlign
 
+/**
+ * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
+ *
+ * Centered horizontally; headline-small typography; on-surface color.
+ */
 @Composable
-fun TextOnSurface(
-    text: String,
-    modifier: Modifier = Modifier,
-    textAlign: TextAlign? = null,
-    style: TextStyle,
-) {
-    TextInternal(
+fun HeadlineText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
         text = text,
         color = MaterialTheme.colorScheme.onSurface,
-        modifier = modifier,
-        textAlign = textAlign,
-        style = style,
+        textAlign = TextAlign.Center,
+        style = MaterialTheme.typography.headlineSmall,
     )
 }
 
+/**
+ * Body-medium typography; on-surface-variant color.
+ */
 @Composable
-fun TextSecondary(
-    text: String,
-    modifier: Modifier = Modifier,
-    textAlign: TextAlign? = null,
-    style: TextStyle,
-) {
-    TextInternal(
-        text = text,
-        color = MaterialTheme.colorScheme.secondary,
-        modifier = modifier,
-        textAlign = textAlign,
-        style = style,
-    )
-}
-
-@Composable
-fun TextOnSurfaceVariant(
-    text: String,
-    modifier: Modifier = Modifier,
-    textAlign: TextAlign? = null,
-    style: TextStyle,
-) {
-    TextInternal(
+fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
         text = text,
         color = MaterialTheme.colorScheme.onSurfaceVariant,
-        modifier = modifier,
-        textAlign = textAlign,
-        style = style,
+        style = MaterialTheme.typography.bodyMedium,
     )
 }
 
+/**
+ * Body-small typography; on-surface-variant color.
+ */
 @Composable
-private fun TextInternal(
-    text: String,
-    color: Color,
-    modifier: Modifier,
-    textAlign: TextAlign?,
-    style: TextStyle,
-) {
-    Text(text = text, color = color, modifier = modifier, textAlign = textAlign, style = style)
+fun BodySmallText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        style = MaterialTheme.typography.bodySmall,
+    )
+}
+
+/**
+ * Title-large typography; on-surface color.
+ */
+@Composable
+fun LargeTitleText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.titleLarge,
+    )
+}
+
+/**
+ * Title-small typography; on-surface color.
+ */
+@Composable
+fun SmallTitleText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.titleSmall,
+    )
+}
+
+/**
+ * Title-small typography.
+ */
+@Composable
+fun SectionHeaderText(text: String, modifier: Modifier = Modifier, color: Color) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        color = color,
+        style = MaterialTheme.typography.titleSmall,
+    )
+}
+
+/**
+ * Body-medium typography; inverse-on-surface color.
+ */
+@Composable
+fun SnackbarContentText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        color = MaterialTheme.colorScheme.inverseOnSurface,
+        style = MaterialTheme.typography.bodyMedium,
+    )
+}
+
+/**
+ * Label-large typography; inverse-primary color.
+ */
+@Composable
+fun SnackbarActionText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        color = MaterialTheme.colorScheme.inversePrimary,
+        style = MaterialTheme.typography.labelLarge,
+    )
+}
+
+/**
+ * Label-large typography; on-surface-variant color; centered.
+ */
+@Composable
+fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        textAlign = TextAlign.Center,
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        style = MaterialTheme.typography.labelLarge,
+    )
+}
+
+/**
+ * Label-large typography; color following parent spec; centered.
+ */
+@Composable
+fun LargeLabelText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        modifier = modifier.wrapContentHeight(),
+        text = text,
+        textAlign = TextAlign.Center,
+        style = MaterialTheme.typography.labelLarge,
+    )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 942eb49..379b3e3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -8,28 +8,19 @@
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.material3.Divider
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.outlined.NewReleases
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -38,9 +29,6 @@
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.input.PasswordVisualTransformation
-import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.CredentialSelectorViewModel
@@ -49,15 +37,21 @@
 import com.android.credentialmanager.common.CredentialType
 import com.android.credentialmanager.common.ProviderActivityState
 import com.android.credentialmanager.common.ui.ActionButton
+import com.android.credentialmanager.common.ui.BodyMediumText
+import com.android.credentialmanager.common.ui.BodySmallText
 import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.common.ui.CredentialContainerCard
+import com.android.credentialmanager.common.ui.CtaButtonRow
 import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.EntryListColumn
+import com.android.credentialmanager.common.ui.HeadlineIcon
+import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.ModalBottomSheet
-import com.android.credentialmanager.common.ui.TextOnSurface
-import com.android.credentialmanager.common.ui.TextSecondary
-import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
-import com.android.credentialmanager.common.ui.ContainerCard
-import com.android.credentialmanager.common.ui.ToggleVisibilityButton
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.credentialmanager.common.ui.MoreAboutPasskeySectionHeader
+import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
+import com.android.credentialmanager.common.ui.SheetContainerCard
+import com.android.credentialmanager.common.ui.PasskeyBenefitRow
+import com.android.credentialmanager.common.ui.HeadlineText
 
 @Composable
 fun CreateCredentialScreen(
@@ -73,7 +67,7 @@
             when (viewModel.uiState.providerActivityState) {
                 ProviderActivityState.NOT_APPLICABLE -> {
                     when (createCredentialUiState.currentScreenState) {
-                        CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
+                        CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard(
                             onConfirm = viewModel::createFlowOnConfirmIntro,
                             onLearnMore = viewModel::createFlowOnLearnMore,
                         )
@@ -157,119 +151,59 @@
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun ConfirmationCard(
+fun PasskeyIntroCard(
     onConfirm: () -> Unit,
     onLearnMore: () -> Unit,
 ) {
-    ContainerCard() {
-        Column() {
-            val onboardingImageResource = remember {
-                mutableStateOf(R.drawable.ic_passkeys_onboarding)
-            }
-            if (isSystemInDarkTheme()) {
-                onboardingImageResource.value = R.drawable.ic_passkeys_onboarding_dark
-            } else {
-                onboardingImageResource.value = R.drawable.ic_passkeys_onboarding
-            }
-            Image(
-                painter = painterResource(onboardingImageResource.value),
-                contentDescription = null,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
-            )
-            TextOnSurface(
-                text = stringResource(R.string.passkey_creation_intro_title),
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-                textAlign = TextAlign.Center,
-            )
-            Divider(
-                thickness = 16.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                Image(
-                    modifier = Modifier.size(24.dp),
-                    painter = painterResource(R.drawable.ic_passkeys_onboarding_password),
-                    contentDescription = null
-                )
-                TextSecondary(
-                    text = stringResource(R.string.passkey_creation_intro_body_password),
-                    style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
-                )
-            }
-            Divider(
-                thickness = 16.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                Image(
-                    modifier = Modifier.size(24.dp),
-                    painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
-                    contentDescription = null
-                )
-                TextSecondary(
-                    text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
-                    style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
-                )
-            }
-            Divider(
-                thickness = 16.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                Image(
-                    modifier = Modifier.size(24.dp),
-                    painter = painterResource(R.drawable.ic_passkeys_onboarding_device),
-                    contentDescription = null
-                )
-                TextSecondary(
-                    text = stringResource(R.string.passkey_creation_intro_body_device),
-                    style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
-                )
-            }
-            Divider(
-                thickness = 32.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
+    SheetContainerCard {
+        val onboardingImageResource = remember {
+            mutableStateOf(R.drawable.ic_passkeys_onboarding)
+        }
+        if (isSystemInDarkTheme()) {
+            onboardingImageResource.value = R.drawable.ic_passkeys_onboarding_dark
+        } else {
+            onboardingImageResource.value = R.drawable.ic_passkeys_onboarding
+        }
+        Image(
+            painter = painterResource(onboardingImageResource.value),
+            contentDescription = null,
+            modifier = Modifier
+                .align(alignment = Alignment.CenterHorizontally).size(316.dp, 168.dp)
+        )
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        HeadlineText(text = stringResource(R.string.passkey_creation_intro_title))
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        PasskeyBenefitRow(
+            leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_password),
+            text = stringResource(R.string.passkey_creation_intro_body_password),
+        )
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        PasskeyBenefitRow(
+            leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
+            text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
+        )
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        PasskeyBenefitRow(
+            leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_device),
+            text = stringResource(R.string.passkey_creation_intro_body_device),
+        )
+        Divider(thickness = 24.dp, color = Color.Transparent)
+
+        CtaButtonRow(
+            leftButton = {
                 ActionButton(
                     stringResource(R.string.string_learn_more),
                     onClick = onLearnMore
                 )
+            },
+            rightButton = {
                 ConfirmButton(
                     stringResource(R.string.string_continue),
                     onClick = onConfirm
                 )
-            }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 18.dp)
-            )
-        }
+            },
+        )
     }
 }
 
@@ -283,102 +217,68 @@
     onDisabledProvidersSelected: () -> Unit,
     onMoreOptionsSelected: () -> Unit,
 ) {
-    ContainerCard() {
-        Column() {
-            Icon(
-                bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
-                contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
+    SheetContainerCard {
+        HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap())
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        HeadlineText(
+            text = stringResource(
+                R.string.choose_provider_title,
+                when (requestDisplayInfo.type) {
+                    CredentialType.PASSKEY ->
+                        stringResource(R.string.passkeys)
+                    CredentialType.PASSWORD ->
+                        stringResource(R.string.passwords)
+                    CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
+                }
             )
-            TextOnSurface(
-                text = stringResource(
-                    R.string.choose_provider_title,
-                    when (requestDisplayInfo.type) {
-                        CredentialType.PASSKEY ->
-                            stringResource(R.string.passkeys)
-                        CredentialType.PASSWORD ->
-                            stringResource(R.string.passwords)
-                        CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
-                    }
-                ),
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-                textAlign = TextAlign.Center,
-            )
-            Divider(
-                thickness = 16.dp,
-                color = Color.Transparent
-            )
-            TextSecondary(
-                text = stringResource(R.string.choose_provider_body),
-                style = MaterialTheme.typography.bodyLarge,
-                modifier = Modifier.padding(horizontal = 28.dp),
-            )
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier.padding(
-                    start = 24.dp,
-                    end = 24.dp,
-                    top = 24.dp,
-                    bottom = if (hasRemoteEntry) 24.dp else 16.dp
-                ).align(alignment = Alignment.CenterHorizontally),
+        )
+        Divider(thickness = 24.dp, color = Color.Transparent)
+
+        BodyMediumText(text = stringResource(R.string.choose_provider_body))
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        CredentialContainerCard {
+            LazyColumn(
+                verticalArrangement = Arrangement.spacedBy(2.dp)
             ) {
-                LazyColumn(
-                    verticalArrangement = Arrangement.spacedBy(2.dp)
-                ) {
-                    sortedCreateOptionsPairs.forEach { entry ->
-                        item {
-                            MoreOptionsInfoRow(
-                                requestDisplayInfo = requestDisplayInfo,
-                                providerInfo = entry.second,
-                                createOptionInfo = entry.first,
-                                onOptionSelected = {
-                                    onOptionSelected(
-                                        ActiveEntry(
-                                            entry.second,
-                                            entry.first
-                                        )
-                                    )
-                                }
-                            )
-                        }
-                    }
+                sortedCreateOptionsPairs.forEach { entry ->
                     item {
-                        MoreOptionsDisabledProvidersRow(
-                            disabledProviders = disabledProviderList,
-                            onDisabledProvidersSelected =
-                            onDisabledProvidersSelected,
+                        MoreOptionsInfoRow(
+                            requestDisplayInfo = requestDisplayInfo,
+                            providerInfo = entry.second,
+                            createOptionInfo = entry.first,
+                            onOptionSelected = {
+                                onOptionSelected(
+                                    ActiveEntry(
+                                        entry.second,
+                                        entry.first
+                                    )
+                                )
+                            }
                         )
                     }
                 }
+                item {
+                    MoreOptionsDisabledProvidersRow(
+                        disabledProviders = disabledProviderList,
+                        onDisabledProvidersSelected = onDisabledProvidersSelected,
+                    )
+                }
             }
-            if (hasRemoteEntry) {
-                Divider(
-                    thickness = 24.dp,
-                    color = Color.Transparent
-                )
-                Row(
-                    horizontalArrangement = Arrangement.Start,
-                    modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-                ) {
+        }
+        if (hasRemoteEntry) {
+            Divider(thickness = 24.dp, color = Color.Transparent)
+            CtaButtonRow(
+                leftButton = {
                     ActionButton(
                         stringResource(R.string.string_more_options),
                         onMoreOptionsSelected
                     )
                 }
-            }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent,
             )
         }
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsSelectionCard(
     requestDisplayInfo: RequestDisplayInfo,
@@ -393,158 +293,103 @@
     onDisabledProvidersSelected: () -> Unit,
     onRemoteEntrySelected: (BaseEntry) -> Unit,
 ) {
-    ContainerCard() {
-        Column() {
-            TopAppBar(
-                title = {
-                    TextOnSurface(
-                        text =
-                        stringResource(
-                            R.string.save_credential_to_title,
-                            when (requestDisplayInfo.type) {
-                                CredentialType.PASSKEY ->
-                                    stringResource(R.string.passkey)
-                                CredentialType.PASSWORD ->
-                                    stringResource(R.string.password)
-                                CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
-                            }
-                        ),
-                        style = MaterialTheme.typography.titleMedium,
-                    )
-                },
-                navigationIcon = {
-                    IconButton(
-                        onClick =
-                        if (isFromProviderSelection)
-                            onBackProviderSelectionButtonSelected
-                        else onBackCreationSelectionButtonSelected
-                    ) {
-                        Icon(
-                            Icons.Filled.ArrowBack,
-                            stringResource(R.string.accessibility_back_arrow_button)
+    SheetContainerCard(topAppBar = {
+        MoreOptionTopAppBar(
+            text = stringResource(
+                R.string.save_credential_to_title,
+                when (requestDisplayInfo.type) {
+                    CredentialType.PASSKEY ->
+                        stringResource(R.string.passkey)
+                    CredentialType.PASSWORD ->
+                        stringResource(R.string.password)
+                    CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
+                }
+            ),
+            onNavigationIconClicked =
+            if (isFromProviderSelection) onBackProviderSelectionButtonSelected
+            else onBackCreationSelectionButtonSelected,
+        )
+    }) {
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        CredentialContainerCard {
+            EntryListColumn {
+                // Only in the flows with default provider(not first time use) we can show the
+                // createOptions here, or they will be shown on ProviderSelectionCard
+                if (hasDefaultProvider) {
+                    sortedCreateOptionsPairs.forEach { entry ->
+                        item {
+                            MoreOptionsInfoRow(
+                                requestDisplayInfo = requestDisplayInfo,
+                                providerInfo = entry.second,
+                                createOptionInfo = entry.first,
+                                onOptionSelected = {
+                                    onOptionSelected(
+                                        ActiveEntry(
+                                            entry.second,
+                                            entry.first
+                                        )
+                                    )
+                                })
+                        }
+                    }
+                    item {
+                        MoreOptionsDisabledProvidersRow(
+                            disabledProviders = disabledProviderList,
+                            onDisabledProvidersSelected =
+                            onDisabledProvidersSelected,
                         )
                     }
-                },
-                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
-                modifier = Modifier.padding(top = 12.dp)
-            )
-            Divider(
-                thickness = 8.dp,
-                color = Color.Transparent
-            )
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally)
-            ) {
-                LazyColumn(
-                    verticalArrangement = Arrangement.spacedBy(2.dp)
-                ) {
-                    // Only in the flows with default provider(not first time use) we can show the
-                    // createOptions here, or they will be shown on ProviderSelectionCard
-                    if (hasDefaultProvider) {
-                        sortedCreateOptionsPairs.forEach { entry ->
-                            item {
-                                MoreOptionsInfoRow(
-                                    requestDisplayInfo = requestDisplayInfo,
-                                    providerInfo = entry.second,
-                                    createOptionInfo = entry.first,
-                                    onOptionSelected = {
-                                        onOptionSelected(
-                                            ActiveEntry(
-                                                entry.second,
-                                                entry.first
-                                            )
-                                        )
-                                    })
-                            }
-                        }
+                }
+                enabledProviderList.forEach {
+                    if (it.remoteEntry != null) {
                         item {
-                            MoreOptionsDisabledProvidersRow(
-                                disabledProviders = disabledProviderList,
-                                onDisabledProvidersSelected =
-                                onDisabledProvidersSelected,
+                            RemoteEntryRow(
+                                remoteInfo = it.remoteEntry!!,
+                                onRemoteEntrySelected = onRemoteEntrySelected,
                             )
                         }
-                    }
-                    enabledProviderList.forEach {
-                        if (it.remoteEntry != null) {
-                            item {
-                                RemoteEntryRow(
-                                    remoteInfo = it.remoteEntry!!,
-                                    onRemoteEntrySelected = onRemoteEntrySelected,
-                                )
-                            }
-                            return@forEach
-                        }
+                        return@forEach
                     }
                 }
             }
-            Divider(
-                thickness = 8.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 40.dp)
-            )
         }
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsRowIntroCard(
     providerInfo: EnabledProviderInfo,
     onChangeDefaultSelected: () -> Unit,
     onUseOnceSelected: () -> Unit,
 ) {
-    ContainerCard() {
-        Column() {
-            Icon(
-                Icons.Outlined.NewReleases,
-                contentDescription = null,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(all = 24.dp),
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+    SheetContainerCard {
+        HeadlineIcon(imageVector = Icons.Outlined.NewReleases)
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        HeadlineText(
+            text = stringResource(
+                R.string.use_provider_for_all_title,
+                providerInfo.displayName
             )
-            TextOnSurface(
-                text = stringResource(
-                    R.string.use_provider_for_all_title,
-                    providerInfo.displayName
-                ),
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-                textAlign = TextAlign.Center,
-            )
-            TextSecondary(
-                text = stringResource(R.string.use_provider_for_all_description),
-                style = MaterialTheme.typography.bodyLarge,
-                modifier = Modifier.padding(all = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
+        )
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        BodyMediumText(text = stringResource(R.string.use_provider_for_all_description))
+        CtaButtonRow(
+            leftButton = {
                 ActionButton(
                     stringResource(R.string.use_once),
                     onClick = onUseOnceSelected
                 )
+            },
+            rightButton = {
                 ConfirmButton(
                     stringResource(R.string.set_as_default),
                     onClick = onChangeDefaultSelected
                 )
-            }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 40.dp)
-            )
-        }
+            },
+        )
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CreationSelectionCard(
     requestDisplayInfo: RequestDisplayInfo,
@@ -556,113 +401,82 @@
     onMoreOptionsSelected: () -> Unit,
     hasDefaultProvider: Boolean,
 ) {
-    ContainerCard() {
-        Column() {
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            Icon(
-                bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
-                contentDescription = null,
-                tint = Color.Unspecified,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally).size(32.dp)
-            )
-            TextSecondary(
-                text = providerInfo.displayName,
-                style = MaterialTheme.typography.titleLarge,
-                modifier = Modifier.padding(vertical = 10.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-                textAlign = TextAlign.Center,
-            )
-            TextOnSurface(
-                text = when (requestDisplayInfo.type) {
-                    CredentialType.PASSKEY -> stringResource(
-                        R.string.choose_create_option_passkey_title,
-                        requestDisplayInfo.appName
-                    )
-                    CredentialType.PASSWORD -> stringResource(
-                        R.string.choose_create_option_password_title,
-                        requestDisplayInfo.appName
-                    )
-                    CredentialType.UNKNOWN -> stringResource(
-                        R.string.choose_create_option_sign_in_title,
-                        requestDisplayInfo.appName
-                    )
-                },
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-                textAlign = TextAlign.Center,
-            )
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(all = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-            ) {
-                PrimaryCreateOptionRow(
-                    requestDisplayInfo = requestDisplayInfo,
-                    entryInfo = createOptionInfo,
-                    onOptionSelected = onOptionSelected
+    SheetContainerCard {
+        HeadlineIcon(
+            bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+            tint = Color.Unspecified,
+        )
+        Divider(thickness = 4.dp, color = Color.Transparent)
+        LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName)
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        HeadlineText(
+            text = when (requestDisplayInfo.type) {
+                CredentialType.PASSKEY -> stringResource(
+                    R.string.choose_create_option_passkey_title,
+                    requestDisplayInfo.appName
+                )
+                CredentialType.PASSWORD -> stringResource(
+                    R.string.choose_create_option_password_title,
+                    requestDisplayInfo.appName
+                )
+                CredentialType.UNKNOWN -> stringResource(
+                    R.string.choose_create_option_sign_in_title,
+                    requestDisplayInfo.appName
                 )
             }
-            var createOptionsSize = 0
-            var remoteEntry: RemoteInfo? = null
-            enabledProviderList.forEach { enabledProvider ->
-                if (enabledProvider.remoteEntry != null) {
-                    remoteEntry = enabledProvider.remoteEntry
-                }
-                createOptionsSize += enabledProvider.createOptions.size
+        )
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        CredentialContainerCard {
+            PrimaryCreateOptionRow(
+                requestDisplayInfo = requestDisplayInfo,
+                entryInfo = createOptionInfo,
+                onOptionSelected = onOptionSelected
+            )
+        }
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        var createOptionsSize = 0
+        var remoteEntry: RemoteInfo? = null
+        enabledProviderList.forEach { enabledProvider ->
+            if (enabledProvider.remoteEntry != null) {
+                remoteEntry = enabledProvider.remoteEntry
             }
-            val shouldShowMoreOptionsButton = if (!hasDefaultProvider) {
-                // User has already been presented with all options on the default provider
-                // selection screen. Don't show them again. Therefore, only show the more option
-                // button if remote option is present.
-                remoteEntry != null
-            } else {
-                createOptionsSize > 1 || remoteEntry != null
-            }
-            Row(
-                horizontalArrangement =
-                if (shouldShowMoreOptionsButton) Arrangement.SpaceBetween else Arrangement.End,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                if (shouldShowMoreOptionsButton) {
+            createOptionsSize += enabledProvider.createOptions.size
+        }
+        val shouldShowMoreOptionsButton = if (!hasDefaultProvider) {
+            // User has already been presented with all options on the default provider
+            // selection screen. Don't show them again. Therefore, only show the more option
+            // button if remote option is present.
+            remoteEntry != null
+        } else {
+            createOptionsSize > 1 || remoteEntry != null
+        }
+        CtaButtonRow(
+            leftButton = if (shouldShowMoreOptionsButton) {
+                {
                     ActionButton(
                         stringResource(R.string.string_more_options),
-                        onClick = onMoreOptionsSelected
+                        onMoreOptionsSelected
                     )
                 }
+            } else null,
+            rightButton = {
                 ConfirmButton(
                     stringResource(R.string.string_continue),
                     onClick = onConfirm
                 )
-            }
-            if (createOptionInfo.footerDescription != null) {
-                Divider(
-                    thickness = 1.dp,
-                    color = Color.LightGray,
-                    modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 18.dp)
-                )
-                TextSecondary(
-                    text = createOptionInfo.footerDescription,
-                    style = MaterialTheme.typography.bodyLarge,
-                    modifier = Modifier.padding(
-                        start = 29.dp, top = 8.dp, bottom = 18.dp, end = 28.dp
-                    )
-                )
-            }
+            },
+        )
+        if (createOptionInfo.footerDescription != null) {
             Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 16.dp)
+                thickness = 1.dp,
+                color = MaterialTheme.colorScheme.outlineVariant,
+                modifier = Modifier.padding(vertical = 16.dp)
             )
+            BodySmallText(text = createOptionInfo.footerDescription)
         }
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ExternalOnlySelectionCard(
     requestDisplayInfo: RequestDisplayInfo,
@@ -670,148 +484,75 @@
     onOptionSelected: (BaseEntry) -> Unit,
     onConfirm: () -> Unit,
 ) {
-    ContainerCard() {
-        Column() {
-            Icon(
-                painter = painterResource(R.drawable.ic_other_devices),
-                contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(all = 24.dp).size(32.dp)
+    SheetContainerCard {
+        HeadlineIcon(painter = painterResource(R.drawable.ic_other_devices))
+        Divider(thickness = 16.dp, color = Color.Transparent)
+        HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title))
+        Divider(
+            thickness = 24.dp,
+            color = Color.Transparent
+        )
+        CredentialContainerCard {
+            PrimaryCreateOptionRow(
+                requestDisplayInfo = requestDisplayInfo,
+                entryInfo = activeRemoteEntry,
+                onOptionSelected = onOptionSelected
             )
-            TextOnSurface(
-                text = stringResource(R.string.create_passkey_in_other_device_title),
-                style = MaterialTheme.typography.titleMedium,
-                modifier = Modifier.padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-                textAlign = TextAlign.Center,
-            )
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-            ) {
-                PrimaryCreateOptionRow(
-                    requestDisplayInfo = requestDisplayInfo,
-                    entryInfo = activeRemoteEntry,
-                    onOptionSelected = onOptionSelected
-                )
-            }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.End,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
+        }
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        CtaButtonRow(
+            rightButton = {
                 ConfirmButton(
                     stringResource(R.string.string_continue),
                     onClick = onConfirm
                 )
-            }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 16.dp)
-            )
-        }
+            },
+        )
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreAboutPasskeysIntroCard(
     onBackPasskeyIntroButtonSelected: () -> Unit,
 ) {
-    ContainerCard() {
-        Column() {
-            TopAppBar(
-                title = {
-                    TextOnSurface(
-                        text =
-                        stringResource(
-                            R.string.more_about_passkeys_title
-                        ),
-                        style = MaterialTheme.typography.titleMedium,
-                    )
-                },
-                navigationIcon = {
-                    IconButton(
-                        onClick = onBackPasskeyIntroButtonSelected
-                    ) {
-                        Icon(
-                            Icons.Filled.ArrowBack,
-                            stringResource(R.string.accessibility_back_arrow_button)
-                        )
-                    }
-                },
-                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
-                modifier = Modifier.padding(top = 12.dp)
-            )
-            Column(
-                modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 68.dp)
-            ) {
-                TextOnSurfaceVariant(
-                    text = stringResource(R.string.passwordless_technology_title),
-                    style = MaterialTheme.typography.titleLarge,
+    SheetContainerCard(topAppBar = {
+        MoreOptionTopAppBar(
+            text = stringResource(R.string.more_about_passkeys_title),
+            onNavigationIconClicked = onBackPasskeyIntroButtonSelected,
+        )
+    }) {
+        LazyColumn(
+            modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+            verticalArrangement = Arrangement.spacedBy(8.dp)
+        ) {
+            item {
+                MoreAboutPasskeySectionHeader(
+                    text = stringResource(R.string.passwordless_technology_title)
                 )
-                TextSecondary(
-                    text = stringResource(R.string.passwordless_technology_detail),
-                    style = MaterialTheme.typography.bodyMedium,
-                )
-                Divider(
-                    thickness = 24.dp,
-                    color = Color.Transparent
-                )
-                TextOnSurfaceVariant(
-                    text = stringResource(R.string.public_key_cryptography_title),
-                    style = MaterialTheme.typography.titleLarge,
-                )
-                TextSecondary(
-                    text = stringResource(R.string.public_key_cryptography_detail),
-                    style = MaterialTheme.typography.bodyMedium,
-                )
-                Divider(
-                    thickness = 24.dp,
-                    color = Color.Transparent
-                )
-                TextOnSurfaceVariant(
-                    text = stringResource(R.string.improved_account_security_title),
-                    style = MaterialTheme.typography.titleLarge,
-                )
-                TextSecondary(
-                    text = stringResource(R.string.improved_account_security_detail),
-                    style = MaterialTheme.typography.bodyMedium,
-                )
-                Divider(
-                    thickness = 24.dp,
-                    color = Color.Transparent
-                )
-                TextOnSurfaceVariant(
-                    text = stringResource(R.string.seamless_transition_title),
-                    style = MaterialTheme.typography.titleLarge,
-                )
-                TextSecondary(
-                    text = stringResource(R.string.seamless_transition_detail),
-                    style = MaterialTheme.typography.bodyMedium,
-                )
+                BodyMediumText(text = stringResource(R.string.passwordless_technology_detail))
             }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 24.dp)
-            )
+            item {
+                MoreAboutPasskeySectionHeader(
+                    text = stringResource(R.string.public_key_cryptography_title)
+                )
+                BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail))
+            }
+            item {
+                MoreAboutPasskeySectionHeader(
+                    text = stringResource(R.string.improved_account_security_title)
+                )
+                BodyMediumText(text = stringResource(R.string.improved_account_security_detail))
+            }
+            item {
+                MoreAboutPasskeySectionHeader(
+                    text = stringResource(R.string.seamless_transition_title)
+                )
+                BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
+            }
         }
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun PrimaryCreateOptionRow(
     requestDisplayInfo: RequestDisplayInfo,
@@ -820,115 +561,37 @@
 ) {
     Entry(
         onClick = { onOptionSelected(entryInfo) },
-        icon = {
-            if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) {
-                Image(
-                    bitmap = entryInfo.profileIcon.toBitmap().asImageBitmap(),
-                    contentDescription = null,
-                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                )
-            } else {
-                Icon(
-                    bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
-                    contentDescription = null,
-                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                )
-            }
+        iconImageBitmap =
+        if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) {
+            entryInfo.profileIcon.toBitmap().asImageBitmap()
+        } else {
+            requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
         },
-        label = {
-            Column() {
-                when (requestDisplayInfo.type) {
-                    CredentialType.PASSKEY -> {
-                        TextOnSurfaceVariant(
-                            text = requestDisplayInfo.title,
-                            style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp, start = 5.dp),
-                        )
-                        TextSecondary(
-                            text = if (requestDisplayInfo.subtitle != null) {
-                                requestDisplayInfo.subtitle + " • " + stringResource(
-                                    R.string.passkey_before_subtitle
-                                )
-                            } else {
-                                stringResource(R.string.passkey_before_subtitle)
-                            },
-                            style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                        )
-                    }
-                    CredentialType.PASSWORD -> {
-                        TextOnSurfaceVariant(
-                            text = requestDisplayInfo.title,
-                            style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp, start = 5.dp),
-                        )
-                        Row(
-                            modifier = Modifier.fillMaxWidth().padding(
-                                top = 4.dp, bottom = 16.dp,
-                                start = 5.dp
-                            ),
-                            verticalAlignment = Alignment.CenterVertically
-                        ) {
-                            val visualTransformation = remember { PasswordVisualTransformation() }
-                            // This subtitle would never be null for create password
-                            val originalPassword by remember {
-                                mutableStateOf(requestDisplayInfo.subtitle ?: "")
-                            }
-                            val displayedPassword = remember {
-                                mutableStateOf(
-                                    visualTransformation.filter(
-                                        AnnotatedString(originalPassword)
-                                    ).text.text
-                                )
-                            }
-                            TextSecondary(
-                                text = displayedPassword.value,
-                                style = MaterialTheme.typography.bodyMedium,
-                                modifier = Modifier.padding(top = 4.dp, bottom = 4.dp),
-                            )
-
-                            ToggleVisibilityButton(modifier = Modifier.padding(start = 4.dp)
-                                .height(24.dp).width(24.dp), onToggle = {
-                                if (it) {
-                                    displayedPassword.value = originalPassword
-                                } else {
-                                    displayedPassword.value = visualTransformation.filter(
-                                        AnnotatedString(originalPassword)
-                                    ).text.text
-                                }
-                            })
-                        }
-                    }
-                    CredentialType.UNKNOWN -> {
-                        if (requestDisplayInfo.subtitle != null) {
-                            TextOnSurfaceVariant(
-                                text = requestDisplayInfo.title,
-                                style = MaterialTheme.typography.titleLarge,
-                                modifier = Modifier.padding(top = 16.dp, start = 5.dp),
-                            )
-                            TextOnSurfaceVariant(
-                                text = requestDisplayInfo.subtitle,
-                                style = MaterialTheme.typography.bodyMedium,
-                                modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                            )
-                        } else {
-                            TextOnSurfaceVariant(
-                                text = requestDisplayInfo.title,
-                                style = MaterialTheme.typography.titleLarge,
-                                modifier = Modifier.padding(
-                                    top = 16.dp, bottom = 16.dp, start = 5.dp
-                                ),
-                            )
-                        }
-                    }
+        shouldApplyIconImageBitmapTint = !(entryInfo is CreateOptionInfo &&
+            entryInfo.profileIcon != null),
+        entryHeadlineText = requestDisplayInfo.title,
+        entrySecondLineText = when (requestDisplayInfo.type) {
+            CredentialType.PASSKEY -> {
+                if (requestDisplayInfo.subtitle != null) {
+                    requestDisplayInfo.subtitle + " • " + stringResource(
+                        R.string.passkey_before_subtitle
+                    )
+                } else {
+                    stringResource(R.string.passkey_before_subtitle)
                 }
             }
-        }
+            // Set passwordValue instead
+            CredentialType.PASSWORD -> null
+            CredentialType.UNKNOWN -> requestDisplayInfo.subtitle
+        },
+        passwordValue =
+        if (requestDisplayInfo.type == CredentialType.PASSWORD)
+        // This subtitle would never be null for create password
+            requestDisplayInfo.subtitle ?: ""
+        else null,
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsInfoRow(
     requestDisplayInfo: RequestDisplayInfo,
@@ -938,93 +601,46 @@
 ) {
     Entry(
         onClick = onOptionSelected,
-        icon = {
-            Image(
-                modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
-                contentDescription = null
-            )
-        },
-        label = {
-            Column() {
-                TextOnSurfaceVariant(
-                    text = providerInfo.displayName,
-                    style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(top = 16.dp, start = 5.dp),
+        iconImageBitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+        entryHeadlineText = providerInfo.displayName,
+        entrySecondLineText = createOptionInfo.userProviderDisplayName,
+        entryThirdLineText =
+        if (requestDisplayInfo.type == CredentialType.PASSKEY ||
+            requestDisplayInfo.type == CredentialType.PASSWORD) {
+            if (createOptionInfo.passwordCount != null &&
+                createOptionInfo.passkeyCount != null
+            ) {
+                stringResource(
+                    R.string.more_options_usage_passwords_passkeys,
+                    createOptionInfo.passwordCount,
+                    createOptionInfo.passkeyCount
                 )
-                if (createOptionInfo.userProviderDisplayName != null) {
-                    TextSecondary(
-                        text = createOptionInfo.userProviderDisplayName,
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(start = 5.dp),
-                    )
-                }
-                if (requestDisplayInfo.type == CredentialType.PASSKEY ||
-                    requestDisplayInfo.type == CredentialType.PASSWORD
-                ) {
-                    if (createOptionInfo.passwordCount != null &&
-                        createOptionInfo.passkeyCount != null
-                    ) {
-                        TextSecondary(
-                            text =
-                            stringResource(
-                                R.string.more_options_usage_passwords_passkeys,
-                                createOptionInfo.passwordCount,
-                                createOptionInfo.passkeyCount
-                            ),
-                            style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                        )
-                    } else if (createOptionInfo.passwordCount != null) {
-                        TextSecondary(
-                            text =
-                            stringResource(
-                                R.string.more_options_usage_passwords,
-                                createOptionInfo.passwordCount
-                            ),
-                            style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                        )
-                    } else if (createOptionInfo.passkeyCount != null) {
-                        TextSecondary(
-                            text =
-                            stringResource(
-                                R.string.more_options_usage_passkeys,
-                                createOptionInfo.passkeyCount
-                            ),
-                            style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                        )
-                    } else {
-                        Divider(
-                            thickness = 16.dp,
-                            color = Color.Transparent,
-                        )
-                    }
-                } else {
-                    if (createOptionInfo.totalCredentialCount != null) {
-                        TextSecondary(
-                            text =
-                            stringResource(
-                                R.string.more_options_usage_credentials,
-                                createOptionInfo.totalCredentialCount
-                            ),
-                            style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                        )
-                    } else {
-                        Divider(
-                            thickness = 16.dp,
-                            color = Color.Transparent,
-                        )
-                    }
-                }
+            } else if (createOptionInfo.passwordCount != null) {
+                stringResource(
+                    R.string.more_options_usage_passwords,
+                    createOptionInfo.passwordCount
+                )
+            } else if (createOptionInfo.passkeyCount != null) {
+                stringResource(
+                    R.string.more_options_usage_passkeys,
+                    createOptionInfo.passkeyCount
+                )
+            } else {
+                null
             }
-        }
+        } else {
+            if (createOptionInfo.totalCredentialCount != null) {
+                stringResource(
+                    R.string.more_options_usage_credentials,
+                    createOptionInfo.totalCredentialCount
+                )
+            } else {
+                null
+            }
+        },
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsDisabledProvidersRow(
     disabledProviders: List<ProviderInfo>?,
@@ -1033,36 +649,15 @@
     if (disabledProviders != null && disabledProviders.isNotEmpty()) {
         Entry(
             onClick = onDisabledProvidersSelected,
-            icon = {
-                Icon(
-                    Icons.Filled.Add,
-                    contentDescription = null,
-                    modifier = Modifier.padding(start = 16.dp),
-                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                )
+            iconImageVector = Icons.Filled.Add,
+            entryHeadlineText = stringResource(R.string.other_password_manager),
+            entrySecondLineText = disabledProviders.joinToString(separator = " • ") {
+                it.displayName
             },
-            label = {
-                Column() {
-                    TextOnSurfaceVariant(
-                        text = stringResource(R.string.other_password_manager),
-                        style = MaterialTheme.typography.titleLarge,
-                        modifier = Modifier.padding(top = 16.dp, start = 5.dp),
-                    )
-                    // TODO: Update the subtitle once design is confirmed
-                    TextSecondary(
-                        text = disabledProviders.joinToString(separator = " • ") {
-                            it.displayName
-                        },
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                }
-            }
         )
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun RemoteEntryRow(
     remoteInfo: RemoteInfo,
@@ -1070,23 +665,7 @@
 ) {
     Entry(
         onClick = { onRemoteEntrySelected(remoteInfo) },
-        icon = {
-            Icon(
-                painter = painterResource(R.drawable.ic_other_devices),
-                contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.padding(start = 10.dp)
-            )
-        },
-        label = {
-            Column() {
-                TextOnSurfaceVariant(
-                    text = stringResource(R.string.another_device),
-                    style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp)
-                        .align(alignment = Alignment.CenterHorizontally),
-                )
-            }
-        }
+        iconPainter = painterResource(R.drawable.ic_other_devices),
+        entryHeadlineText = stringResource(R.string.another_device),
     )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 5704820..54f8e5c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -20,40 +20,22 @@
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
 import androidx.activity.result.IntentSenderRequest
-
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material.icons.outlined.Lock
 import androidx.compose.material3.Divider
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Snackbar
-import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.CredentialSelectorViewModel
@@ -62,16 +44,18 @@
 import com.android.credentialmanager.common.CredentialType
 import com.android.credentialmanager.common.ProviderActivityState
 import com.android.credentialmanager.common.ui.ActionButton
+import com.android.credentialmanager.common.ui.ActionEntry
 import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.common.ui.CredentialContainerCard
+import com.android.credentialmanager.common.ui.CtaButtonRow
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.ModalBottomSheet
-import com.android.credentialmanager.common.ui.TextOnSurface
-import com.android.credentialmanager.common.ui.TextSecondary
-import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
-import com.android.credentialmanager.common.ui.ContainerCard
-import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
-import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
+import com.android.credentialmanager.common.ui.SheetContainerCard
+import com.android.credentialmanager.common.ui.SnackbarActionText
+import com.android.credentialmanager.common.ui.HeadlineText
+import com.android.credentialmanager.common.ui.CredentialListSectionHeader
+import com.android.credentialmanager.common.ui.Snackbar
 
 @Composable
 fun GetCredentialScreen(
@@ -154,123 +138,100 @@
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
     val authenticationEntryList = providerDisplayInfo.authenticationEntryList
-    ContainerCard() {
-        Column() {
-            TextOnSurface(
-                modifier = Modifier.padding(all = 24.dp),
-                textAlign = TextAlign.Center,
-                style = MaterialTheme.typography.headlineSmall,
-                text = stringResource(
-                    if (sortedUserNameToCredentialEntryList
-                            .size == 1 && authenticationEntryList.isEmpty()
-                    ) {
-                        if (sortedUserNameToCredentialEntryList.first()
-                                .sortedCredentialEntryList.first().credentialType
-                            == CredentialType.PASSKEY
-                        ) R.string.get_dialog_title_use_passkey_for
-                        else R.string.get_dialog_title_use_sign_in_for
-                    } else if (
-                        sortedUserNameToCredentialEntryList
-                            .isEmpty() && authenticationEntryList.size == 1
-                    ) {
-                        R.string.get_dialog_title_use_sign_in_for
-                    } else R.string.get_dialog_title_choose_sign_in_for,
-                    requestDisplayInfo.appName
-                ),
-            )
-
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally)
-            ) {
-                val usernameForCredentialSize = sortedUserNameToCredentialEntryList
-                    .size
-                val authenticationEntrySize = authenticationEntryList.size
-                LazyColumn(
-                    verticalArrangement = Arrangement.spacedBy(2.dp)
+    SheetContainerCard {
+        HeadlineText(
+            text = stringResource(
+                if (sortedUserNameToCredentialEntryList
+                        .size == 1 && authenticationEntryList.isEmpty()
                 ) {
-                    // Show max 4 entries in this primary page
-                    if (usernameForCredentialSize + authenticationEntrySize <= 4) {
-                        items(sortedUserNameToCredentialEntryList) {
-                            CredentialEntryRow(
-                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
-                        items(authenticationEntryList) {
-                            AuthenticationEntryRow(
-                                authenticationEntryInfo = it,
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
-                    } else if (usernameForCredentialSize < 4) {
-                        items(sortedUserNameToCredentialEntryList) {
-                            CredentialEntryRow(
-                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
-                        items(authenticationEntryList.take(4 - usernameForCredentialSize)) {
-                            AuthenticationEntryRow(
-                                authenticationEntryInfo = it,
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
-                    } else {
-                        items(sortedUserNameToCredentialEntryList.take(4)) {
-                            CredentialEntryRow(
-                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
+                    if (sortedUserNameToCredentialEntryList.first()
+                            .sortedCredentialEntryList.first().credentialType
+                        == CredentialType.PASSKEY
+                    ) R.string.get_dialog_title_use_passkey_for
+                    else R.string.get_dialog_title_use_sign_in_for
+                } else if (
+                    sortedUserNameToCredentialEntryList
+                        .isEmpty() && authenticationEntryList.size == 1
+                ) {
+                    R.string.get_dialog_title_use_sign_in_for
+                } else R.string.get_dialog_title_choose_sign_in_for,
+                requestDisplayInfo.appName
+            ),
+        )
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        CredentialContainerCard {
+            val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size
+            val authenticationEntrySize = authenticationEntryList.size
+            LazyColumn(
+                verticalArrangement = Arrangement.spacedBy(2.dp)
+            ) {
+                // Show max 4 entries in this primary page
+                if (usernameForCredentialSize + authenticationEntrySize <= 4) {
+                    items(sortedUserNameToCredentialEntryList) {
+                        CredentialEntryRow(
+                            credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                    items(authenticationEntryList) {
+                        AuthenticationEntryRow(
+                            authenticationEntryInfo = it,
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                } else if (usernameForCredentialSize < 4) {
+                    items(sortedUserNameToCredentialEntryList) {
+                        CredentialEntryRow(
+                            credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                    items(authenticationEntryList.take(4 - usernameForCredentialSize)) {
+                        AuthenticationEntryRow(
+                            authenticationEntryInfo = it,
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                } else {
+                    items(sortedUserNameToCredentialEntryList.take(4)) {
+                        CredentialEntryRow(
+                            credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                            onEntrySelected = onEntrySelected,
+                        )
                     }
                 }
             }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            var totalEntriesCount = sortedUserNameToCredentialEntryList
-                .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList
-                .size + providerInfoList.flatMap { it.actionEntryList }.size
-            if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1
-            // Row horizontalArrangement differs on only one actionButton(should place on most
-            // left)/only one confirmButton(should place on most right)/two buttons exist the same
-            // time(should be one on the left, one on the right)
-            Row(
-                horizontalArrangement =
-                if (totalEntriesCount <= 1 && activeEntry != null) Arrangement.End
-                else if (totalEntriesCount > 1 && activeEntry == null) Arrangement.Start
-                else Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                if (totalEntriesCount > 1) {
+        }
+        Divider(thickness = 24.dp, color = Color.Transparent)
+        var totalEntriesCount = sortedUserNameToCredentialEntryList
+            .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList
+            .size + providerInfoList.flatMap { it.actionEntryList }.size
+        if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1
+        // Row horizontalArrangement differs on only one actionButton(should place on most
+        // left)/only one confirmButton(should place on most right)/two buttons exist the same
+        // time(should be one on the left, one on the right)
+        CtaButtonRow(
+            leftButton = if (totalEntriesCount > 1) {
+                {
                     ActionButton(
                         stringResource(R.string.get_dialog_use_saved_passkey_for),
                         onMoreOptionSelected
                     )
                 }
-                // Only one sign-in options exist
-                if (activeEntry != null) {
+            } else null,
+            rightButton = if (activeEntry != null) { // Only one sign-in options exist
+                {
                     ConfirmButton(
                         stringResource(R.string.string_continue),
                         onClick = onConfirm
                     )
                 }
-            }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 16.dp)
-            )
-        }
+            } else null,
+        )
     }
 }
 
 /** Draws the secondary credential selection page, where all sign-in options are listed. */
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun AllSignInOptionCard(
     providerInfoList: List<ProviderInfo>,
@@ -283,87 +244,53 @@
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
     val authenticationEntryList = providerDisplayInfo.authenticationEntryList
-    ContainerCard() {
-        Column() {
-            TopAppBar(
-                colors = TopAppBarDefaults.topAppBarColors(
-                    containerColor = Color.Transparent,
-                ),
-                title = {
-                    TextOnSurface(
-                        text = stringResource(R.string.get_dialog_title_sign_in_options),
-                        style = MaterialTheme.typography.titleMedium
+    SheetContainerCard(topAppBar = {
+        MoreOptionTopAppBar(
+            text = stringResource(R.string.get_dialog_title_sign_in_options),
+            onNavigationIconClicked = if (isNoAccount) onCancel else onBackButtonClicked,
+        )
+    }) {
+        LazyColumn {
+            // For username
+            items(sortedUserNameToCredentialEntryList) { item ->
+                PerUserNameCredentials(
+                    perUserNameCredentialEntryList = item,
+                    onEntrySelected = onEntrySelected,
+                )
+            }
+            // Locked password manager
+            if (authenticationEntryList.isNotEmpty()) {
+                item {
+                    LockedCredentials(
+                        authenticationEntryList = authenticationEntryList,
+                        onEntrySelected = onEntrySelected,
                     )
-                },
-                navigationIcon = {
-                    IconButton(onClick = if (isNoAccount) onCancel else onBackButtonClicked) {
-                        Icon(
-                            Icons.Filled.ArrowBack,
-                            contentDescription = stringResource(
-                                R.string.accessibility_back_arrow_button)
-                        )
-                    }
-                },
-                modifier = Modifier.padding(top = 12.dp)
-            )
-
-            ContainerCard(
-                shape = MaterialTheme.shapes.large,
-                modifier = Modifier
-                    .padding(start = 24.dp, end = 24.dp, bottom = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-            ) {
-                LazyColumn(
-                    verticalArrangement = Arrangement.spacedBy(8.dp)
-                ) {
-                    // For username
-                    items(sortedUserNameToCredentialEntryList) { item ->
-                        PerUserNameCredentials(
-                            perUserNameCredentialEntryList = item,
-                            onEntrySelected = onEntrySelected,
-                        )
-                    }
-                    // Locked password manager
-                    if (authenticationEntryList.isNotEmpty()) {
-                        item {
-                            LockedCredentials(
-                                authenticationEntryList = authenticationEntryList,
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
-                    }
-                    item {
-                        Divider(
-                            thickness = 8.dp,
-                            color = Color.Transparent,
-                        )
-                    }
-                    // From another device
-                    val remoteEntry = providerDisplayInfo.remoteEntry
-                    if (remoteEntry != null) {
-                        item {
-                            RemoteEntryCard(
-                                remoteEntry = remoteEntry,
-                                onEntrySelected = onEntrySelected,
-                            )
-                        }
-                    }
-                    item {
-                        Divider(
-                            thickness = 1.dp,
-                            color = Color.LightGray,
-                            modifier = Modifier.padding(top = 16.dp)
-                        )
-                    }
-                    // Manage sign-ins (action chips)
-                    item {
-                        ActionChips(
-                            providerInfoList = providerInfoList,
-                            onEntrySelected = onEntrySelected
-                        )
-                    }
                 }
             }
+            // From another device
+            val remoteEntry = providerDisplayInfo.remoteEntry
+            if (remoteEntry != null) {
+                item {
+                    RemoteEntryCard(
+                        remoteEntry = remoteEntry,
+                        onEntrySelected = onEntrySelected,
+                    )
+                }
+            }
+            item {
+                Divider(
+                    thickness = 1.dp,
+                    color = Color.LightGray,
+                    modifier = Modifier.padding(top = 16.dp)
+                )
+            }
+            // Manage sign-ins (action chips)
+            item {
+                ActionChips(
+                    providerInfoList = providerInfoList,
+                    onEntrySelected = onEntrySelected
+                )
+            }
         }
     }
 }
@@ -381,17 +308,11 @@
         return
     }
 
-    TextSecondary(
-        text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
-        style = MaterialTheme.typography.titleLarge,
-        modifier = Modifier.padding(vertical = 8.dp)
+    CredentialListSectionHeader(
+        text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
     )
-    // TODO: tweak padding.
-    ContainerCard(
-        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-        shape = MaterialTheme.shapes.medium,
-    ) {
-        Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+    CredentialContainerCard {
+        Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
             actionChips.forEach {
                 ActionEntryRow(it, onEntrySelected)
             }
@@ -404,38 +325,20 @@
     remoteEntry: RemoteEntryInfo,
     onEntrySelected: (BaseEntry) -> Unit,
 ) {
-    TextSecondary(
-        text = stringResource(R.string.get_dialog_heading_from_another_device),
-        style = MaterialTheme.typography.titleLarge,
-        modifier = Modifier.padding(vertical = 8.dp)
+    CredentialListSectionHeader(
+        text = stringResource(R.string.get_dialog_heading_from_another_device)
     )
-    ContainerCard(
-        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-        shape = MaterialTheme.shapes.medium,
-    ) {
+    CredentialContainerCard {
         Column(
             modifier = Modifier.fillMaxWidth().wrapContentHeight(),
             verticalArrangement = Arrangement.spacedBy(2.dp),
         ) {
             Entry(
                 onClick = { onEntrySelected(remoteEntry) },
-                icon = {
-                    Icon(
-                        painter = painterResource(R.drawable.ic_other_devices),
-                        contentDescription = null,
-                        tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                        modifier = Modifier.padding(start = 10.dp)
-                    )
-                },
-                label = {
-                    TextOnSurfaceVariant(
-                        text = stringResource(
-                            R.string.get_dialog_option_headline_use_a_different_device),
-                        style = MaterialTheme.typography.titleLarge,
-                        modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp)
-                            .align(alignment = Alignment.CenterHorizontally)
-                    )
-                }
+                iconPainter = painterResource(R.drawable.ic_other_devices),
+                entryHeadlineText = stringResource(
+                    R.string.get_dialog_option_headline_use_a_different_device
+                ),
             )
         }
     }
@@ -446,15 +349,10 @@
     authenticationEntryList: List<AuthenticationEntryInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
 ) {
-    TextSecondary(
-        text = stringResource(R.string.get_dialog_heading_locked_password_managers),
-        style = MaterialTheme.typography.labelLarge,
-        modifier = Modifier.padding(vertical = 8.dp)
+    CredentialListSectionHeader(
+        text = stringResource(R.string.get_dialog_heading_locked_password_managers)
     )
-    ContainerCard(
-        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-        shape = MaterialTheme.shapes.medium,
-    ) {
+    CredentialContainerCard {
         Column(
             modifier = Modifier.fillMaxWidth().wrapContentHeight(),
             verticalArrangement = Arrangement.spacedBy(2.dp),
@@ -471,17 +369,12 @@
     perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
     onEntrySelected: (BaseEntry) -> Unit,
 ) {
-    TextSecondary(
+    CredentialListSectionHeader(
         text = stringResource(
             R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
-        ),
-        style = MaterialTheme.typography.titleLarge,
-        modifier = Modifier.padding(vertical = 8.dp)
+        )
     )
-    ContainerCard(
-        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-        shape = MaterialTheme.shapes.medium,
-    ) {
+    CredentialContainerCard {
         Column(
             modifier = Modifier.fillMaxWidth().wrapContentHeight(),
             verticalArrangement = Arrangement.spacedBy(2.dp),
@@ -500,53 +393,28 @@
 ) {
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
-        icon = {
-            if (credentialEntryInfo.icon != null) {
-                Image(
-                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                    bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
-                    contentDescription = null,
-                )
-            } else {
-                Icon(
-                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                    painter = painterResource(R.drawable.ic_other_sign_in),
-                    contentDescription = null,
-                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
-                )
-            }
+        iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
+        // Fall back to iconPainter if iconImageBitmap isn't available
+        iconPainter =
+        if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in)
+        else null,
+        entryHeadlineText = credentialEntryInfo.userName,
+        entrySecondLineText = if (
+            credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
+            "••••••••••••"
+        } else {
+            if (TextUtils.isEmpty(credentialEntryInfo.displayName))
+                credentialEntryInfo.credentialTypeDisplayName
+            else
+                credentialEntryInfo.credentialTypeDisplayName +
+                    stringResource(
+                        R.string.get_dialog_sign_in_type_username_separator
+                    ) +
+                    credentialEntryInfo.displayName
         },
-        label = {
-            Column() {
-                // TODO: fix the text values.
-                TextOnSurfaceVariant(
-                    text = credentialEntryInfo.userName,
-                    style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(top = 16.dp, start = 5.dp)
-                )
-                TextSecondary(
-                    text = if (
-                        credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
-                        "••••••••••••"
-                    } else {
-                        if (TextUtils.isEmpty(credentialEntryInfo.displayName))
-                            credentialEntryInfo.credentialTypeDisplayName
-                        else
-                            credentialEntryInfo.credentialTypeDisplayName +
-                                stringResource(
-                                    R.string.get_dialog_sign_in_type_username_separator
-                                ) +
-                                credentialEntryInfo.displayName
-                    },
-                    style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(bottom = 16.dp, start = 5.dp)
-                )
-            }
-        }
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun AuthenticationEntryRow(
     authenticationEntryInfo: AuthenticationEntryInfo,
@@ -554,144 +422,66 @@
 ) {
     Entry(
         onClick = { onEntrySelected(authenticationEntryInfo) },
-        icon = {
-            Image(
-                modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
-                contentDescription = null
-            )
-        },
-        label = {
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp),
-            ) {
-                Column() {
-                    TextOnSurfaceVariant(
-                        text = authenticationEntryInfo.title,
-                        style = MaterialTheme.typography.titleLarge,
-                        modifier = Modifier.padding(top = 16.dp)
-                    )
-                    TextSecondary(
-                        text = stringResource(
-                            if (authenticationEntryInfo.isUnlockedAndEmpty)
-                                R.string.locked_credential_entry_label_subtext_no_sign_in
-                            else R.string.locked_credential_entry_label_subtext_tap_to_unlock
-                    ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp)
-                    )
-                }
-                if (!authenticationEntryInfo.isUnlockedAndEmpty) {
-                    Icon(
-                        Icons.Outlined.Lock,
-                        null,
-                        Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp),
-                    )
-                }
-            }
-        }
+        iconImageBitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
+        entryHeadlineText = authenticationEntryInfo.title,
+        entrySecondLineText = stringResource(
+            if (authenticationEntryInfo.isUnlockedAndEmpty)
+                R.string.locked_credential_entry_label_subtext_no_sign_in
+            else R.string.locked_credential_entry_label_subtext_tap_to_unlock
+        ),
+        isLockedAuthEntry = !authenticationEntryInfo.isUnlockedAndEmpty,
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ActionEntryRow(
     actionEntryInfo: ActionEntryInfo,
     onEntrySelected: (BaseEntry) -> Unit,
 ) {
-    TransparentBackgroundEntry(
-        icon = {
-            Image(
-                modifier = Modifier.padding(start = 10.dp).size(24.dp),
-                bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
-                contentDescription = null,
-            )
-        },
-        label = {
-            Column() {
-                TextOnSurfaceVariant(
-                    text = actionEntryInfo.title,
-                    style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(start = 8.dp),
-                )
-                if (actionEntryInfo.subTitle != null) {
-                    TextSecondary(
-                        text = actionEntryInfo.subTitle,
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(start = 8.dp),
-                    )
-                }
-            }
-        },
+    ActionEntry(
+        iconImageBitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
+        entryHeadlineText = actionEntryInfo.title,
+        entrySecondLineText = actionEntryInfo.subTitle,
         onClick = { onEntrySelected(actionEntryInfo) },
     )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun RemoteCredentialSnackBarScreen(
     onClick: (Boolean) -> Unit,
     onCancel: () -> Unit,
 ) {
-    // TODO: Change the height, width and position according to the design
     Snackbar(
-        modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
-        shape = EntryShape.FullMediumRoundedCorner,
-        containerColor = LocalAndroidColorScheme.current.colorBackground,
-        contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
         action = {
             TextButton(
+                modifier = Modifier.padding(top = 12.dp, bottom = 12.dp, start = 16.dp),
                 onClick = { onClick(true) },
             ) {
-                Text(text = stringResource(R.string.snackbar_action))
-            }
-        },
-        dismissAction = {
-            IconButton(onClick = onCancel) {
-                Icon(
-                    Icons.Filled.Close,
-                    contentDescription = stringResource(
-                        R.string.accessibility_close_button
-                    ),
-                    tint = LocalAndroidColorScheme.current.colorAccentTertiary
+                SnackbarActionText(
+                    text = stringResource(R.string.snackbar_action),
+                    Modifier.padding(vertical = 6.dp)
                 )
             }
         },
-    ) {
-        Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
-    }
+        onDismiss = onCancel,
+        contentText = stringResource(R.string.get_dialog_use_saved_passkey_for),
+    )
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun EmptyAuthEntrySnackBarScreen(
     authenticationEntryList: List<AuthenticationEntryInfo>,
     onCancel: () -> Unit,
     onLastLockedAuthEntryNotFound: () -> Unit,
 ) {
-    val lastLocked = authenticationEntryList.firstOrNull({it.isLastUnlocked})
+    val lastLocked = authenticationEntryList.firstOrNull({ it.isLastUnlocked })
     if (lastLocked == null) {
         onLastLockedAuthEntryNotFound()
         return
     }
 
-    // TODO: Change the height, width and position according to the design
     Snackbar(
-        modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
-        shape = EntryShape.FullMediumRoundedCorner,
-        containerColor = LocalAndroidColorScheme.current.colorBackground,
-        contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-        dismissAction = {
-            IconButton(onClick = onCancel) {
-                Icon(
-                    Icons.Filled.Close,
-                    contentDescription = stringResource(R.string.accessibility_close_button),
-                    tint = LocalAndroidColorScheme.current.colorAccentTertiary
-                )
-            }
-        },
-    ) {
-        Text(text = stringResource(R.string.no_sign_in_info_in, lastLocked.title))
-    }
+        onDismiss = onCancel,
+        contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
+    )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 49415c0..02e7557 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -96,6 +96,7 @@
     pendingIntent: PendingIntent?,
     fillInIntent: Intent?,
     val title: String,
+    val providerDisplayName: String,
     val icon: Drawable,
     // The entry had been unlocked and turned out to be empty. Used to determine whether to
     // show "Tap to unlock" or "No sign-in info" for this entry.
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
index 15ae329..120e493 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -22,12 +22,14 @@
 import androidx.compose.ui.graphics.Color
 import com.android.internal.R
 
+/** File copied from PlatformComposeCore. */
+
 /** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
 val LocalAndroidColorScheme =
     staticCompositionLocalOf<AndroidColorScheme> {
         throw IllegalStateException(
             "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
-                    "Composable surrounded by a CredentialSelectorTheme {}."
+                "Composable surrounded by a PlatformTheme {}."
         )
     }
 
@@ -38,7 +40,6 @@
  * most of the colors in this class will be removed in favor of their M3 counterpart.
  */
 class AndroidColorScheme internal constructor(context: Context) {
-
     val colorPrimary = getColor(context, R.attr.colorPrimary)
     val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
     val colorAccent = getColor(context, R.attr.colorAccent)
@@ -66,10 +67,12 @@
     val colorForeground = getColor(context, R.attr.colorForeground)
     val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
 
-    private fun getColor(context: Context, attr: Int): Color {
-        val ta = context.obtainStyledAttributes(intArrayOf(attr))
-        @ColorInt val color = ta.getColor(0, 0)
-        ta.recycle()
-        return Color(color)
+    companion object {
+        fun getColor(context: Context, attr: Int): Color {
+            val ta = context.obtainStyledAttributes(intArrayOf(attr))
+            @ColorInt val color = ta.getColor(0, 0)
+            ta.recycle()
+            return Color(color)
+        }
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
index abb4bfb..c923845 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
@@ -1,14 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.ui.theme
 
+import android.annotation.AttrRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
 
-val Grey100 = Color(0xFFF1F3F4)
-val Purple200 = Color(0xFFBB86FC)
-val Purple500 = Color(0xFF6200EE)
-val Purple700 = Color(0xFF3700B3)
-val Teal200 = Color(0xFF03DAC5)
-val lightColorAccentSecondary = Color(0xFFC2E7FF)
-val lightBackgroundColor = Color(0xFFF0F0F0)
-val lightSurface1 = Color(0xFF6991D6)
-val textColorSecondary = Color(0xFF40484B)
-val textColorPrimary = Color(0xFF191C1D)
+/** Read the [Color] from the given [attribute]. */
+@Composable
+@ReadOnlyComposable
+fun colorAttr(@AttrRes attribute: Int): Color {
+    return AndroidColorScheme.getColor(LocalContext.current, attribute)
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
new file mode 100644
index 0000000..662199a
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens
+import com.android.credentialmanager.ui.theme.typography.TypefaceNames
+import com.android.credentialmanager.ui.theme.typography.TypefaceTokens
+import com.android.credentialmanager.ui.theme.typography.TypographyTokens
+import com.android.credentialmanager.ui.theme.typography.platformTypography
+
+/** File copied from PlatformComposeCore. */
+
+/** The Material 3 theme that should wrap all Platform Composables. */
+@Composable
+fun PlatformTheme(
+    isDarkTheme: Boolean = isSystemInDarkTheme(),
+    content: @Composable () -> Unit,
+) {
+    val context = LocalContext.current
+
+    // TODO(b/230605885): Define our color scheme.
+    val colorScheme =
+        if (isDarkTheme) {
+            dynamicDarkColorScheme(context)
+        } else {
+            dynamicLightColorScheme(context)
+        }
+    val androidColorScheme = AndroidColorScheme(context)
+    val typefaceNames = remember(context) { TypefaceNames.get(context) }
+    val typography =
+        remember(typefaceNames) {
+            platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
+        }
+
+    MaterialTheme(colorScheme, typography = typography) {
+        CompositionLocalProvider(
+            LocalAndroidColorScheme provides androidColorScheme,
+        ) {
+            content()
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
deleted file mode 100644
index 3ca0e44..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.android.credentialmanager.ui.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.platform.LocalContext
-
-@Composable
-fun CredentialSelectorTheme(
-  darkTheme: Boolean = isSystemInDarkTheme(),
-  content: @Composable () -> Unit
-) {
-  val context = LocalContext.current
-
-  val colorScheme =
-    if (darkTheme) {
-      dynamicDarkColorScheme(context)
-    } else {
-      dynamicLightColorScheme(context)
-    }
-  val androidColorScheme = AndroidColorScheme(context)
-  val typography = Typography
-
-  MaterialTheme(
-    colorScheme,
-    typography = typography,
-    shapes = Shapes
-  ) {
-    CompositionLocalProvider(
-      LocalAndroidColorScheme provides androidColorScheme,
-    ) {
-      content()
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
deleted file mode 100644
index e09abbb..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.android.credentialmanager.ui.theme
-
-import androidx.compose.material3.Typography
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.sp
-
-// Set of Material typography styles to start with
-val Typography = Typography(
-  titleMedium = TextStyle(
-    fontFamily = FontFamily.Default,
-    fontWeight = FontWeight.Normal,
-    fontSize = 24.sp,
-    lineHeight = 32.sp,
-  ),
-  bodyLarge = TextStyle(
-    fontFamily = FontFamily.Default,
-    fontWeight = FontWeight.Normal,
-    fontSize = 14.sp,
-    lineHeight = 20.sp,
-  ),
-  bodyMedium = TextStyle(
-    fontFamily = FontFamily.Default,
-    fontWeight = FontWeight.Normal,
-    fontSize = 14.sp,
-    lineHeight = 20.sp,
-    color = textColorSecondary
-  ),
-  labelLarge = TextStyle(
-    fontFamily = FontFamily.Default,
-    fontWeight = FontWeight.Medium,
-    fontSize = 14.sp,
-    lineHeight = 20.sp,
-  ),
-  titleLarge = TextStyle(
-    fontFamily = FontFamily.Default,
-    fontWeight = FontWeight.Medium,
-    fontSize = 16.sp,
-    lineHeight = 24.sp,
-    color = textColorPrimary
-  ),
-
-  /* Other default text styles to override
-    button = TextStyle(
-        fontFamily = FontFamily.Default,
-        fontWeight = FontWeight.W500,
-        fontSize = 14.sp
-    ),
-    caption = TextStyle(
-        fontFamily = FontFamily.Default,
-        fontWeight = FontWeight.Normal,
-        fontSize = 12.sp
-    )
-    */
-)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
new file mode 100644
index 0000000..984e4f1
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ui.theme.typography
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+
+/** File copied from PlatformComposeCore. */
+
+/**
+ * The typography for Platform Compose code.
+ *
+ * Do not use directly and call [MaterialTheme.typography] instead to access the different text
+ * styles.
+ */
+internal fun platformTypography(typographyTokens: TypographyTokens): Typography {
+    return Typography(
+        displayLarge = typographyTokens.displayLarge,
+        displayMedium = typographyTokens.displayMedium,
+        displaySmall = typographyTokens.displaySmall,
+        headlineLarge = typographyTokens.headlineLarge,
+        headlineMedium = typographyTokens.headlineMedium,
+        headlineSmall = typographyTokens.headlineSmall,
+        titleLarge = typographyTokens.titleLarge,
+        titleMedium = typographyTokens.titleMedium,
+        titleSmall = typographyTokens.titleSmall,
+        bodyLarge = typographyTokens.bodyLarge,
+        bodyMedium = typographyTokens.bodyMedium,
+        bodySmall = typographyTokens.bodySmall,
+        labelLarge = typographyTokens.labelLarge,
+        labelMedium = typographyTokens.labelMedium,
+        labelSmall = typographyTokens.labelSmall,
+    )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
new file mode 100644
index 0000000..b2dd207
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ui.theme.typography
+
+import androidx.compose.ui.unit.sp
+
+/** File copied from PlatformComposeCore. */
+internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
+    val bodyLargeFont = typefaceTokens.plain
+    val bodyLargeLineHeight = 24.0.sp
+    val bodyLargeSize = 16.sp
+    val bodyLargeTracking = 0.0.sp
+    val bodyLargeWeight = TypefaceTokens.WeightRegular
+    val bodyMediumFont = typefaceTokens.plain
+    val bodyMediumLineHeight = 20.0.sp
+    val bodyMediumSize = 14.sp
+    val bodyMediumTracking = 0.0.sp
+    val bodyMediumWeight = TypefaceTokens.WeightRegular
+    val bodySmallFont = typefaceTokens.plain
+    val bodySmallLineHeight = 16.0.sp
+    val bodySmallSize = 12.sp
+    val bodySmallTracking = 0.1.sp
+    val bodySmallWeight = TypefaceTokens.WeightRegular
+    val displayLargeFont = typefaceTokens.brand
+    val displayLargeLineHeight = 64.0.sp
+    val displayLargeSize = 57.sp
+    val displayLargeTracking = 0.0.sp
+    val displayLargeWeight = TypefaceTokens.WeightRegular
+    val displayMediumFont = typefaceTokens.brand
+    val displayMediumLineHeight = 52.0.sp
+    val displayMediumSize = 45.sp
+    val displayMediumTracking = 0.0.sp
+    val displayMediumWeight = TypefaceTokens.WeightRegular
+    val displaySmallFont = typefaceTokens.brand
+    val displaySmallLineHeight = 44.0.sp
+    val displaySmallSize = 36.sp
+    val displaySmallTracking = 0.0.sp
+    val displaySmallWeight = TypefaceTokens.WeightRegular
+    val headlineLargeFont = typefaceTokens.brand
+    val headlineLargeLineHeight = 40.0.sp
+    val headlineLargeSize = 32.sp
+    val headlineLargeTracking = 0.0.sp
+    val headlineLargeWeight = TypefaceTokens.WeightRegular
+    val headlineMediumFont = typefaceTokens.brand
+    val headlineMediumLineHeight = 36.0.sp
+    val headlineMediumSize = 28.sp
+    val headlineMediumTracking = 0.0.sp
+    val headlineMediumWeight = TypefaceTokens.WeightRegular
+    val headlineSmallFont = typefaceTokens.brand
+    val headlineSmallLineHeight = 32.0.sp
+    val headlineSmallSize = 24.sp
+    val headlineSmallTracking = 0.0.sp
+    val headlineSmallWeight = TypefaceTokens.WeightRegular
+    val labelLargeFont = typefaceTokens.plain
+    val labelLargeLineHeight = 20.0.sp
+    val labelLargeSize = 14.sp
+    val labelLargeTracking = 0.0.sp
+    val labelLargeWeight = TypefaceTokens.WeightMedium
+    val labelMediumFont = typefaceTokens.plain
+    val labelMediumLineHeight = 16.0.sp
+    val labelMediumSize = 12.sp
+    val labelMediumTracking = 0.1.sp
+    val labelMediumWeight = TypefaceTokens.WeightMedium
+    val labelSmallFont = typefaceTokens.plain
+    val labelSmallLineHeight = 16.0.sp
+    val labelSmallSize = 11.sp
+    val labelSmallTracking = 0.1.sp
+    val labelSmallWeight = TypefaceTokens.WeightMedium
+    val titleLargeFont = typefaceTokens.brand
+    val titleLargeLineHeight = 28.0.sp
+    val titleLargeSize = 22.sp
+    val titleLargeTracking = 0.0.sp
+    val titleLargeWeight = TypefaceTokens.WeightRegular
+    val titleMediumFont = typefaceTokens.plain
+    val titleMediumLineHeight = 24.0.sp
+    val titleMediumSize = 16.sp
+    val titleMediumTracking = 0.0.sp
+    val titleMediumWeight = TypefaceTokens.WeightMedium
+    val titleSmallFont = typefaceTokens.plain
+    val titleSmallLineHeight = 20.0.sp
+    val titleSmallSize = 14.sp
+    val titleSmallTracking = 0.0.sp
+    val titleSmallWeight = TypefaceTokens.WeightMedium
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
new file mode 100644
index 0000000..3cc761f
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package com.android.credentialmanager.ui.theme.typography
+
+import android.content.Context
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.font.DeviceFontFamilyName
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+
+/** File copied from PlatformComposeCore. */
+internal class TypefaceTokens(typefaceNames: TypefaceNames) {
+    companion object {
+        val WeightMedium = FontWeight.Medium
+        val WeightRegular = FontWeight.Normal
+    }
+
+    private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
+    private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
+
+    val brand =
+        FontFamily(
+            Font(brandFont, weight = WeightMedium),
+            Font(brandFont, weight = WeightRegular),
+        )
+    val plain =
+        FontFamily(
+            Font(plainFont, weight = WeightMedium),
+            Font(plainFont, weight = WeightRegular),
+        )
+}
+
+internal data class TypefaceNames
+private constructor(
+    val brand: String,
+    val plain: String,
+) {
+    private enum class Config(val configName: String, val default: String) {
+        Brand("config_headlineFontFamily", "sans-serif"),
+        Plain("config_bodyFontFamily", "sans-serif"),
+    }
+
+    companion object {
+        fun get(context: Context): TypefaceNames {
+            return TypefaceNames(
+                brand = getTypefaceName(context, Config.Brand),
+                plain = getTypefaceName(context, Config.Plain),
+            )
+        }
+
+        private fun getTypefaceName(context: Context, config: Config): String {
+            return context
+                .getString(context.resources.getIdentifier(config.configName, "string", "android"))
+                .takeIf { it.isNotEmpty() }
+                ?: config.default
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
new file mode 100644
index 0000000..aadab92
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ui.theme.typography
+
+import androidx.compose.ui.text.TextStyle
+
+/** File copied from PlatformComposeCore. */
+internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
+    val bodyLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.bodyLargeFont,
+            fontWeight = typeScaleTokens.bodyLargeWeight,
+            fontSize = typeScaleTokens.bodyLargeSize,
+            lineHeight = typeScaleTokens.bodyLargeLineHeight,
+            letterSpacing = typeScaleTokens.bodyLargeTracking,
+        )
+    val bodyMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.bodyMediumFont,
+            fontWeight = typeScaleTokens.bodyMediumWeight,
+            fontSize = typeScaleTokens.bodyMediumSize,
+            lineHeight = typeScaleTokens.bodyMediumLineHeight,
+            letterSpacing = typeScaleTokens.bodyMediumTracking,
+        )
+    val bodySmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.bodySmallFont,
+            fontWeight = typeScaleTokens.bodySmallWeight,
+            fontSize = typeScaleTokens.bodySmallSize,
+            lineHeight = typeScaleTokens.bodySmallLineHeight,
+            letterSpacing = typeScaleTokens.bodySmallTracking,
+        )
+    val displayLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.displayLargeFont,
+            fontWeight = typeScaleTokens.displayLargeWeight,
+            fontSize = typeScaleTokens.displayLargeSize,
+            lineHeight = typeScaleTokens.displayLargeLineHeight,
+            letterSpacing = typeScaleTokens.displayLargeTracking,
+        )
+    val displayMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.displayMediumFont,
+            fontWeight = typeScaleTokens.displayMediumWeight,
+            fontSize = typeScaleTokens.displayMediumSize,
+            lineHeight = typeScaleTokens.displayMediumLineHeight,
+            letterSpacing = typeScaleTokens.displayMediumTracking,
+        )
+    val displaySmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.displaySmallFont,
+            fontWeight = typeScaleTokens.displaySmallWeight,
+            fontSize = typeScaleTokens.displaySmallSize,
+            lineHeight = typeScaleTokens.displaySmallLineHeight,
+            letterSpacing = typeScaleTokens.displaySmallTracking,
+        )
+    val headlineLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.headlineLargeFont,
+            fontWeight = typeScaleTokens.headlineLargeWeight,
+            fontSize = typeScaleTokens.headlineLargeSize,
+            lineHeight = typeScaleTokens.headlineLargeLineHeight,
+            letterSpacing = typeScaleTokens.headlineLargeTracking,
+        )
+    val headlineMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.headlineMediumFont,
+            fontWeight = typeScaleTokens.headlineMediumWeight,
+            fontSize = typeScaleTokens.headlineMediumSize,
+            lineHeight = typeScaleTokens.headlineMediumLineHeight,
+            letterSpacing = typeScaleTokens.headlineMediumTracking,
+        )
+    val headlineSmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.headlineSmallFont,
+            fontWeight = typeScaleTokens.headlineSmallWeight,
+            fontSize = typeScaleTokens.headlineSmallSize,
+            lineHeight = typeScaleTokens.headlineSmallLineHeight,
+            letterSpacing = typeScaleTokens.headlineSmallTracking,
+        )
+    val labelLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.labelLargeFont,
+            fontWeight = typeScaleTokens.labelLargeWeight,
+            fontSize = typeScaleTokens.labelLargeSize,
+            lineHeight = typeScaleTokens.labelLargeLineHeight,
+            letterSpacing = typeScaleTokens.labelLargeTracking,
+        )
+    val labelMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.labelMediumFont,
+            fontWeight = typeScaleTokens.labelMediumWeight,
+            fontSize = typeScaleTokens.labelMediumSize,
+            lineHeight = typeScaleTokens.labelMediumLineHeight,
+            letterSpacing = typeScaleTokens.labelMediumTracking,
+        )
+    val labelSmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.labelSmallFont,
+            fontWeight = typeScaleTokens.labelSmallWeight,
+            fontSize = typeScaleTokens.labelSmallSize,
+            lineHeight = typeScaleTokens.labelSmallLineHeight,
+            letterSpacing = typeScaleTokens.labelSmallTracking,
+        )
+    val titleLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.titleLargeFont,
+            fontWeight = typeScaleTokens.titleLargeWeight,
+            fontSize = typeScaleTokens.titleLargeSize,
+            lineHeight = typeScaleTokens.titleLargeLineHeight,
+            letterSpacing = typeScaleTokens.titleLargeTracking,
+        )
+    val titleMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.titleMediumFont,
+            fontWeight = typeScaleTokens.titleMediumWeight,
+            fontSize = typeScaleTokens.titleMediumSize,
+            lineHeight = typeScaleTokens.titleMediumLineHeight,
+            letterSpacing = typeScaleTokens.titleMediumTracking,
+        )
+    val titleSmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.titleSmallFont,
+            fontWeight = typeScaleTokens.titleSmallWeight,
+            fontSize = typeScaleTokens.titleSmallSize,
+            lineHeight = typeScaleTokens.titleSmallLineHeight,
+            letterSpacing = typeScaleTokens.titleSmallTracking,
+        )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 5e6c614..0552c40 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
@@ -46,6 +45,7 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -100,11 +100,16 @@
                 contentDescription = null,
                 modifier = Modifier.size(SettingsDimension.itemIconSize),
             )
-            Spacer(Modifier.height(4.dp))
-            Text(
-                text = actionButton.text,
-                style = MaterialTheme.typography.labelMedium,
-            )
+            Box(
+                modifier = Modifier.padding(top = 4.dp).fillMaxHeight(),
+                contentAlignment = Alignment.Center,
+            ) {
+                Text(
+                    text = actionButton.text,
+                    textAlign = TextAlign.Center,
+                    style = MaterialTheme.typography.labelMedium,
+                )
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 18b20733..1a7d896 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.graphics.drawable.Drawable
-import android.os.UserManager
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
@@ -28,6 +27,7 @@
 import com.android.settingslib.Utils
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
@@ -42,23 +42,25 @@
         val context = LocalContext.current
         return produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
             withContext(Dispatchers.IO) {
-                if (isClonedAppPage || isCloneApp(context, app)) {
-                    value = context.getString(R.string.cloned_app_info_label, loadLabel(app))
+                value = if (isClonedAppPage || isCloneApp(context, app)) {
+                    context.getString(R.string.cloned_app_info_label, loadLabel(app))
                 } else {
-                    value = loadLabel(app)
+                    loadLabel(app)
                 }
             }
         }
     }
 
     private fun isCloneApp(context: Context, app: ApplicationInfo): Boolean {
-        val userManager = context.getSystemService(UserManager::class.java)!!
-        val userInfo = userManager.getUserInfo(app.userId)
+        val userInfo = context.userManager.getUserInfo(app.userId)
         return userInfo != null && userInfo.isCloneProfile
     }
 
     @Composable
     fun produceIcon(app: ApplicationInfo): State<Drawable?>
+
+    @Composable
+    fun produceIconContentDescription(app: ApplicationInfo): State<String?>
 }
 
 internal class AppRepositoryImpl(private val context: Context) : AppRepository {
@@ -69,8 +71,22 @@
     @Composable
     override fun produceIcon(app: ApplicationInfo) =
         produceState<Drawable?>(initialValue = null, app) {
-            withContext(Dispatchers.Default) {
+            withContext(Dispatchers.IO) {
                 value = Utils.getBadgedIcon(context, app)
             }
         }
+
+    @Composable
+    override fun produceIconContentDescription(app: ApplicationInfo) =
+        produceState<String?>(initialValue = null, app) {
+            withContext(Dispatchers.IO) {
+                value = when {
+                    context.userManager.isManagedProfile(app.userId) -> {
+                        context.getString(R.string.category_work)
+                    }
+
+                    else -> null
+                }
+            }
+        }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index 602df54..e3ea2e7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
@@ -50,7 +51,8 @@
                 .padding(
                     horizontal = SettingsDimension.itemPaddingStart,
                     vertical = SettingsDimension.itemPaddingVertical,
-                ),
+                )
+                .semantics(mergeDescendants = true) {},
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             val app = packageInfo.applicationInfo
@@ -93,8 +95,8 @@
     val appRepository = rememberAppRepository()
     Image(
         painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
-        contentDescription = null,
-        modifier = Modifier.size(size)
+        contentDescription = appRepository.produceIconContentDescription(app).value,
+        modifier = Modifier.size(size),
     )
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index fc40aed..4f0cdde 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -103,6 +103,9 @@
 
         @Composable
         override fun produceIcon(app: ApplicationInfo) = stateOf(null)
+
+        @Composable
+        override fun produceIconContentDescription(app: ApplicationInfo) = stateOf(null)
     }
 
     private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
new file mode 100644
index 0000000..26caa01
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.UserManager
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppRepositoryTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var userManager: UserManager
+
+    private lateinit var appRepository: AppRepositoryImpl
+
+    @Before
+    fun setUp() {
+        whenever(context.userManager).thenReturn(userManager)
+        appRepository = AppRepositoryImpl(context)
+    }
+
+    @Test
+    fun produceIconContentDescription_workProfile() {
+        whenever(userManager.isManagedProfile(APP.userId)).thenReturn(true)
+
+        val contentDescription = produceIconContentDescription()
+
+        assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work))
+    }
+
+    @Test
+    fun produceIconContentDescription_personalProfile() {
+        whenever(userManager.isManagedProfile(APP.userId)).thenReturn(false)
+
+        val contentDescription = produceIconContentDescription()
+
+        assertThat(contentDescription.value).isNull()
+    }
+
+    private fun produceIconContentDescription(): State<String?> {
+        var contentDescription: State<String?> = stateOf(null)
+        composeTestRule.setContent {
+            contentDescription = appRepository.produceIconContentDescription(APP)
+        }
+        composeTestRule.delay()
+        return contentDescription
+    }
+
+    private companion object {
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            uid = UID
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 888b09f..e846480 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -575,9 +575,15 @@
 
     /** Get the corresponding adaptive icon drawable. */
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
+        UserManager um = context.getSystemService(UserManager.class);
+        boolean isClone = um.getProfiles(user.getIdentifier()).stream()
+                .anyMatch(profile ->
+                        profile.isCloneProfile() && profile.id == user.getIdentifier());
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
             return iconFactory
-                    .createBadgedIconBitmap(icon, new IconOptions().setUser(user))
+                    .createBadgedIconBitmap(
+                            icon,
+                            new IconOptions().setUser(user).setIsCloneProfile(isClone))
                     .newIcon(context);
         }
     }
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 346462d..1ac20471 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -48,7 +48,6 @@
         "test/**/*.java",
         "src/android/provider/settings/backup/*",
         "src/android/provider/settings/validators/*",
-        "src/com/android/providers/settings/GenerationRegistry.java",
         "src/com/android/providers/settings/SettingsBackupAgent.java",
         "src/com/android/providers/settings/SettingsState.java",
         "src/com/android/providers/settings/SettingsHelper.java",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 7f3b0ff..5617331 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -17,19 +17,19 @@
 package com.android.providers.settings;
 
 import android.os.Bundle;
+import android.os.UserManager;
 import android.provider.Settings;
-import android.util.ArrayMap;
 import android.util.MemoryIntArray;
 import android.util.Slog;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 
 /**
  * This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates shared memory regions which
+ * on a per user basis and updates a shared memory region which
  * client processes can read to determine if their local caches are
  * stale.
  */
@@ -40,187 +40,138 @@
 
     private final Object mLock;
 
-    // Key -> backingStore mapping
     @GuardedBy("mLock")
-    private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
-
-    // Key -> (String->Index map) mapping
-    @GuardedBy("mLock")
-    private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    // Maximum number of backing stores allowed
-    static final int NUM_MAX_BACKING_STORE = 8;
+    private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
 
     @GuardedBy("mLock")
-    private int mNumBackingStore = 0;
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    // Maximum size of an individual backing store
-    static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
+    private MemoryIntArray mBackingStore;
 
     public GenerationRegistry(Object lock) {
         mLock = lock;
     }
 
-    /**
-     *  Increment the generation number if the setting is already cached in the backing stores.
-     *  Otherwise, do nothing.
-     */
-    public void incrementGeneration(int key, String name) {
-        final boolean isConfig =
-                (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
-        // Only store the prefix if the mutated setting is a config
-        final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
+    public void incrementGeneration(int key) {
         synchronized (mLock) {
-            final MemoryIntArray backingStore = getBackingStoreLocked(key,
-                    /* createIfNotExist= */ false);
-            if (backingStore == null) {
-                return;
-            }
-            try {
-                final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
-                        backingStore, /* createIfNotExist= */ false);
-                if (index < 0) {
-                    return;
+            MemoryIntArray backingStore = getBackingStoreLocked();
+            if (backingStore != null) {
+                try {
+                    final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
+                    if (index >= 0) {
+                        final int generation = backingStore.get(index) + 1;
+                        backingStore.set(index, generation);
+                    }
+                } catch (IOException e) {
+                    Slog.e(LOG_TAG, "Error updating generation id", e);
+                    destroyBackingStore();
                 }
-                final int generation = backingStore.get(index) + 1;
-                backingStore.set(index, generation);
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
-                            + " key:" + SettingsState.keyToString(key)
-                            + " at index:" + index);
-                }
-            } catch (IOException e) {
-                Slog.e(LOG_TAG, "Error updating generation id", e);
-                destroyBackingStoreLocked(key);
             }
         }
     }
 
-    /**
-     *  Return the backing store's reference, the index and the current generation number
-     *  of a cached setting. If it was not in the backing store, first create the entry in it before
-     *  returning the result.
-     */
-    public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
+    public void addGenerationData(Bundle bundle, int key) {
         synchronized (mLock) {
-            final MemoryIntArray backingStore = getBackingStoreLocked(key,
-                    /* createIfNotExist= */ true);
-            if (backingStore == null) {
-                // Error accessing existing backing store or no new backing store is available
-                return;
-            }
+            MemoryIntArray backingStore = getBackingStoreLocked();
             try {
-                final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
-                        backingStore, /* createIfNotExist= */ true);
-                if (index < 0) {
-                    // Should not happen unless having error accessing the backing store
-                    return;
-                }
-                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                        backingStore);
-                bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
-                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                        backingStore.get(index));
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Exported index:" + index
-                            + " for setting:" + indexMapKey
-                            + " key:" + SettingsState.keyToString(key));
+                if (backingStore != null) {
+                    final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
+                    if (index >= 0) {
+                        bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+                                backingStore);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+                                backingStore.get(index));
+                        if (DEBUG) {
+                            Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
+                                    + SettingsProvider.keyToString(key));
+                        }
+                    }
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Error adding generation data", e);
-                destroyBackingStoreLocked(key);
+                destroyBackingStore();
             }
         }
     }
 
     public void onUserRemoved(int userId) {
-        final int secureKey = SettingsState.makeKey(
-                SettingsState.SETTINGS_TYPE_SECURE, userId);
-        final int systemKey = SettingsState.makeKey(
-                SettingsState.SETTINGS_TYPE_SYSTEM, userId);
         synchronized (mLock) {
-            if (mKeyToIndexMapMap.containsKey(secureKey)) {
-                destroyBackingStoreLocked(secureKey);
-                mKeyToIndexMapMap.remove(secureKey);
-                mNumBackingStore = mNumBackingStore - 1;
-            }
-            if (mKeyToIndexMapMap.containsKey(systemKey)) {
-                destroyBackingStoreLocked(systemKey);
-                mKeyToIndexMapMap.remove(systemKey);
-                mNumBackingStore = mNumBackingStore - 1;
+            MemoryIntArray backingStore = getBackingStoreLocked();
+            if (backingStore != null && mKeyToIndexMap.size() > 0) {
+                try {
+                    final int secureKey = SettingsProvider.makeKey(
+                            SettingsProvider.SETTINGS_TYPE_SECURE, userId);
+                    resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
+
+                    final int systemKey = SettingsProvider.makeKey(
+                            SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
+                    resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
+                } catch (IOException e) {
+                    Slog.e(LOG_TAG, "Error cleaning up for user", e);
+                    destroyBackingStore();
+                }
             }
         }
     }
 
     @GuardedBy("mLock")
-    private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
-        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
-        if (!createIfNotExist) {
-            return backingStore;
-        }
-        if (backingStore == null) {
+    private MemoryIntArray getBackingStoreLocked() {
+        if (mBackingStore == null) {
+            // One for the config table, one for the global table, two for system
+            // and secure tables for a managed profile (managed profile is not
+            // included in the max user count), ten for partially deleted users if
+            // users are quickly removed, and twice max user count for system and
+            // secure.
+            final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
             try {
-                if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
-                    Slog.e(LOG_TAG, "Error creating backing store - at capacity");
-                    return null;
-                }
-                backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
-                mKeyToBackingStoreMap.put(key, backingStore);
-                mNumBackingStore += 1;
+                mBackingStore = new MemoryIntArray(size);
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Created backing store for "
-                            + SettingsState.keyToString(key) + " on user: "
-                            + SettingsState.getUserIdFromKey(key));
+                    Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Error creating generation tracker", e);
             }
         }
-        return backingStore;
+        return mBackingStore;
     }
 
-    @GuardedBy("mLock")
-    private void destroyBackingStoreLocked(int key) {
-        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
-        if (backingStore != null) {
+    private void destroyBackingStore() {
+        if (mBackingStore != null) {
             try {
-                backingStore.close();
+                mBackingStore.close();
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
+                    Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Cannot close generation memory array", e);
             }
-            mKeyToBackingStoreMap.remove(key);
+            mBackingStore = null;
         }
     }
 
-    private static int getKeyIndexLocked(int key, String indexMapKey,
-            ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
-            MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
-        ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
-        if (nameToIndexMap == null) {
-            if (!createIfNotExist) {
-                return -1;
+    private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
+            MemoryIntArray backingStore) throws IOException {
+        final int index = keyToIndexMap.get(key, -1);
+        if (index >= 0) {
+            keyToIndexMap.delete(key);
+            backingStore.set(index, 0);
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
+                        + SettingsProvider.keyToString(key));
             }
-            nameToIndexMap = new ArrayMap<>();
-            keyToIndexMapMap.put(key, nameToIndexMap);
         }
-        int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
+    }
+
+    private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
+            MemoryIntArray backingStore) throws IOException {
+        int index = keyToIndexMap.get(key, -1);
         if (index < 0) {
-            if (!createIfNotExist) {
-                return -1;
-            }
             index = findNextEmptyIndex(backingStore);
             if (index >= 0) {
                 backingStore.set(index, 1);
-                nameToIndexMap.put(indexMapKey, index);
+                keyToIndexMap.append(key, index);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
-                            + " of type:" + SettingsState.keyToString(key)
-                            + " on user:" + SettingsState.getUserIdFromKey(key));
+                    Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
+                            + SettingsProvider.keyToString(key));
                 }
             } else {
                 Slog.e(LOG_TAG, "Could not allocate generation index");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index d7c7130..683c08e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,7 +35,6 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
-import static com.android.providers.settings.SettingsState.makeKey;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -376,6 +375,10 @@
     @GuardedBy("mLock")
     private boolean mSyncConfigDisabledUntilReboot;
 
+    public static int makeKey(int type, int userId) {
+        return SettingsState.makeKey(type, userId);
+    }
+
     public static int getTypeFromKey(int key) {
         return SettingsState.getTypeFromKey(key);
     }
@@ -384,6 +387,9 @@
         return SettingsState.getUserIdFromKey(key);
     }
 
+    public static String keyToString(int key) {
+        return SettingsState.keyToString(key);
+    }
     @ChangeId
     @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
     private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -547,7 +553,7 @@
 
             case Settings.CALL_METHOD_LIST_CONFIG: {
                 String prefix = getSettingPrefix(args);
-                Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
+                Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
                         isTrackingGeneration(args));
                 reportDeviceConfigAccess(prefix);
                 return result;
@@ -1313,7 +1319,6 @@
         return false;
     }
 
-    @NonNull
     private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2311,8 +2316,7 @@
                 "get/set setting for user", null);
     }
 
-    private Bundle packageValueForCallResult(@Nullable Setting setting,
-            boolean trackingGeneration) {
+    private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
         if (!trackingGeneration) {
             if (setting == null || setting.isNull()) {
                 return NULL_SETTING_BUNDLE;
@@ -2323,26 +2327,19 @@
         result.putString(Settings.NameValueTable.VALUE,
                 !setting.isNull() ? setting.getValue() : null);
 
-        if (setting != null && !setting.isNull()) {
-            // No need to track generation if the setting doesn't exist
-            synchronized (mLock) {
-                mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey(),
-                        setting.getName());
-            }
-        }
+        mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
         return result;
     }
 
-    private Bundle packageValuesForCallResult(String prefix,
-            @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
+    private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
+            boolean trackingGeneration) {
         Bundle result = new Bundle();
         result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
-        if (trackingGeneration && !keyValues.isEmpty()) {
-            // No need to track generation if the namespace is empty
+        if (trackingGeneration) {
             synchronized (mLock) {
                 mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
-                        mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
-                                UserHandle.USER_SYSTEM).mKey, prefix);
+                        mSettingsRegistry.getSettingsLocked(
+                                SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
             }
         }
 
@@ -3452,7 +3449,7 @@
 
         private void notifyForSettingsChange(int key, String name) {
             // Increment the generation first, so observers always see the new value
-            mGenerationRegistry.incrementGeneration(key, name);
+            mGenerationRegistry.incrementGeneration(key);
 
             if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
                 final long token = Binder.clearCallingIdentity();
@@ -3490,7 +3487,7 @@
                 List<String> changedSettings) {
 
             // Increment the generation first, so observers always see the new value
-            mGenerationRegistry.incrementGeneration(key, prefix);
+            mGenerationRegistry.incrementGeneration(key);
 
             StringBuilder stringBuilder = new StringBuilder(prefix);
             for (int i = 0; i < changedSettings.size(); ++i) {
@@ -3516,7 +3513,7 @@
                     if (profileId != userId) {
                         final int key = makeKey(type, profileId);
                         // Increment the generation first, so observers always see the new value
-                        mGenerationRegistry.incrementGeneration(key, name);
+                        mGenerationRegistry.incrementGeneration(key);
                         mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
                                 profileId, 0, uri).sendToTarget();
                     }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
deleted file mode 100644
index d34fe694..0000000
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.providers.settings;
-
-import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY;
-import static android.provider.Settings.CALL_METHOD_GENERATION_KEY;
-import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Bundle;
-import android.util.MemoryIntArray;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-
-@RunWith(AndroidJUnit4.class)
-public class GenerationRegistryTest {
-    @Test
-    public void testGenerationsWithRegularSetting() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
-        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
-        final String testSecureSetting = "test_secure_setting";
-        Bundle b = new Bundle();
-        // IncrementGeneration should have no effect on a non-cached setting.
-        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
-        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
-        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
-        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
-        // Default index is 0 and generation is only 1 despite early calls of incrementGeneration
-        checkBundle(b, 0, 1, false);
-
-        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
-        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
-        // Index is still 0 and generation is now 2; also check direct array access
-        assertThat(getArray(b).get(0)).isEqualTo(2);
-
-        final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0);
-        final String testSystemSetting = "test_system_setting";
-        generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
-        // Default index is 0 and generation is 1 for another backingStore (system)
-        checkBundle(b, 0, 1, false);
-
-        final String testSystemSetting2 = "test_system_setting2";
-        generationRegistry.addGenerationData(b, systemKey, testSystemSetting2);
-        // Second system setting index is 1 and default generation is 1
-        checkBundle(b, 1, 1, false);
-
-        generationRegistry.incrementGeneration(systemKey, testSystemSetting);
-        generationRegistry.incrementGeneration(systemKey, testSystemSetting);
-        generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
-        // First system setting generation now incremented to 3
-        checkBundle(b, 0, 3, false);
-
-        final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10);
-        generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
-        // User 10 has a new set of backingStores
-        checkBundle(b, 0, 1, false);
-
-        // Check user removal
-        generationRegistry.onUserRemoved(10);
-        generationRegistry.incrementGeneration(systemKey2, testSystemSetting);
-
-        // Removed user should not affect existing caches
-        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
-        assertThat(getArray(b).get(0)).isEqualTo(2);
-
-        // IncrementGeneration should have no effect for a non-cached user
-        b.clear();
-        checkBundle(b, -1, -1, true);
-        // AddGeneration should create new backing store for the non-cached user
-        generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
-        checkBundle(b, 0, 1, false);
-    }
-
-    @Test
-    public void testGenerationsWithConfigSetting() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
-        final String prefix = "test_namespace/";
-        final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
-
-        Bundle b = new Bundle();
-        generationRegistry.addGenerationData(b, configKey, prefix);
-        checkBundle(b, 0, 1, false);
-
-        final String setting = "test_namespace/test_setting";
-        // Check that the generation of the prefix is incremented correctly
-        generationRegistry.incrementGeneration(configKey, setting);
-        generationRegistry.addGenerationData(b, configKey, prefix);
-        checkBundle(b, 0, 2, false);
-    }
-
-    @Test
-    public void testMaxNumBackingStores() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
-        final String testSecureSetting = "test_secure_setting";
-        Bundle b = new Bundle();
-        for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
-            b.clear();
-            final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
-            generationRegistry.addGenerationData(b, key, testSecureSetting);
-            checkBundle(b, 0, 1, false);
-        }
-        b.clear();
-        final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
-                GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
-        generationRegistry.addGenerationData(b, key, testSecureSetting);
-        // Should fail to add generation because the number of backing stores has reached limit
-        checkBundle(b, -1, -1, true);
-        // Remove one user should free up a backing store
-        generationRegistry.onUserRemoved(0);
-        generationRegistry.addGenerationData(b, key, testSecureSetting);
-        checkBundle(b, 0, 1, false);
-    }
-
-    @Test
-    public void testMaxSizeBackingStore() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
-        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
-        final String testSecureSetting = "test_secure_setting";
-        Bundle b = new Bundle();
-        for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) {
-            generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i);
-            checkBundle(b, i, 1, false);
-        }
-        b.clear();
-        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
-        // Should fail to increase index because the number of entries in the backing store has
-        // reached the limit
-        checkBundle(b, -1, -1, true);
-        // Shouldn't affect other cached entries
-        generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0");
-        checkBundle(b, 0, 1, false);
-    }
-
-    private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
-            throws IOException {
-        final MemoryIntArray array = getArray(b);
-        if (isNull) {
-            assertThat(array).isNull();
-        } else {
-            assertThat(array).isNotNull();
-        }
-        final int index = b.getInt(
-                CALL_METHOD_GENERATION_INDEX_KEY, -1);
-        assertThat(index).isEqualTo(expectedIndex);
-        final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1);
-        assertThat(generation).isEqualTo(expectedGeneration);
-        if (!isNull) {
-            // Read into the result array with the result index should match the result generation
-            assertThat(array.get(index)).isEqualTo(generation);
-        }
-    }
-
-    private MemoryIntArray getArray(Bundle b) {
-        return b.getParcelable(
-                CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class);
-    }
-}
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 8ba1ff3..37b8ae0 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -76,7 +76,10 @@
                     android:layout_height="48dp"
                     android:layout_width="wrap_content"
                     android:layout_gravity="center_vertical"
-                    android:padding="8dp" />
+                    android:padding="8dp"
+                    android:track="@drawable/settingslib_track_selector"
+                    android:thumb="@drawable/settingslib_thumb_selector"
+                    android:theme="@style/MainSwitch.Settingslib"/>
             </com.android.systemui.statusbar.notification.row.AppControlView>
 
             <!-- ChannelRows get added dynamically -->
@@ -101,7 +104,7 @@
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:maxWidth="200dp"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
+                style="@style/Widget.Dialog.Button"/>
             <TextView
                 android:id="@+id/done_button"
                 android:text="@string/inline_ok_button"
@@ -113,7 +116,7 @@
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:layout_alignParentEnd="true"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
+                style="@style/Widget.Dialog.Button"/>
         </RelativeLayout>
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index d03cd7e..190f999 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -85,6 +85,9 @@
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
             android:padding="8dp"
+            android:track="@drawable/settingslib_track_selector"
+            android:thumb="@drawable/settingslib_thumb_selector"
+            android:theme="@style/MainSwitch.Settingslib"
         />
     </LinearLayout>
 </com.android.systemui.statusbar.notification.row.ChannelRow>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index b927155..8690b36 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -62,7 +62,7 @@
      */
     public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
             int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
-            int taskbarSize, boolean isTablet,
+            int taskbarSize, boolean isLargeScreen,
             int currentRotation, boolean isRtl) {
         boolean isRotated = false;
         boolean isOrientationDifferent;
@@ -95,7 +95,7 @@
             canvasScreenRatio = (float) canvasWidth / screenWidthPx;
         }
         scaledTaskbarSize = taskbarSize * canvasScreenRatio;
-        thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+        thumbnailClipHint.bottom = isLargeScreen ? scaledTaskbarSize : 0;
 
         float scale = thumbnailData.scale;
         final float thumbnailScale;
@@ -103,7 +103,7 @@
         // Landscape vs portrait change.
         // Note: Disable rotation in grid layout.
         boolean windowingModeSupportsRotation =
-                thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet;
+                thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isLargeScreen;
         isOrientationDifferent = isOrientationChange(deltaRotate)
                 && windowingModeSupportsRotation;
         if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 77a13bd..751a3f8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -137,7 +137,7 @@
 
     /** @return whether or not {@param context} represents that of a large screen device or not */
     @TargetApi(Build.VERSION_CODES.R)
-    public static boolean isTablet(Context context) {
+    public static boolean isLargeScreen(Context context) {
         final WindowManager windowManager = context.getSystemService(WindowManager.class);
         final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 90f44a7..ae68618 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -22,10 +22,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
 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_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -46,6 +43,8 @@
 import android.window.TransitionInfo;
 import android.window.TransitionInfo.Change;
 
+import com.android.wm.shell.util.TransitionUtil;
+
 import java.util.ArrayList;
 import java.util.function.Predicate;
 
@@ -75,7 +74,7 @@
     private static void setupLeash(@NonNull SurfaceControl leash,
             @NonNull TransitionInfo.Change change, int layer,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
-        boolean isOpening = info.getType() == TRANSIT_OPEN || info.getType() == TRANSIT_TO_FRONT;
+        final boolean isOpening = TransitionUtil.isOpeningType(info.getType());
         // Put animating stuff above this line and put static stuff below it.
         int zSplitLine = info.getChanges().size();
         // changes should be ordered top-to-bottom in z
@@ -88,7 +87,7 @@
                 absBounds.top - info.getRootOffset().y);
 
         // Put all the OPEN/SHOW on top
-        if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+        if (TransitionUtil.isOpeningType(mode)) {
             if (isOpening) {
                 t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
                 if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
@@ -99,7 +98,7 @@
                 // put on bottom and leave it visible
                 t.setLayer(leash, zSplitLine - layer);
             }
-        } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+        } else if (TransitionUtil.isClosingType(mode)) {
             if (isOpening) {
                 // put on bottom and leave visible
                 t.setLayer(leash, zSplitLine - layer);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 80b9758..70a36ce 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -19,11 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
 
@@ -50,6 +46,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 
@@ -183,7 +180,7 @@
                     final RemoteAnimationTarget target = newTarget(change,
                             info.getChanges().size() - i, info, t, mLeashMap);
                     apps.add(target);
-                    if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) {
+                    if (TransitionUtil.isClosingType(change.getMode())) {
                         // raise closing (pausing) task to "above" layer so it isn't covered
                         t.setLayer(target.leash, info.getChanges().size() * 3 - i);
                         mPausingTasks.add(new TaskState(change, target.leash));
@@ -200,8 +197,7 @@
                     } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                         mRecentsTask = taskInfo.token;
                         mRecentsTaskId = taskInfo.taskId;
-                    } else if (change.getMode() == TRANSIT_OPEN
-                            || change.getMode() == TRANSIT_TO_FRONT) {
+                    } else if (TransitionUtil.isOpeningType(change.getMode())) {
                         mOpeningTasks.add(new TaskState(change, target.leash));
                     }
                 }
@@ -227,7 +223,7 @@
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
                 final boolean isLeafTask = leafTaskFilter.test(change);
-                if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
+                if (TransitionUtil.isOpeningType(change.getMode())) {
                     if (mRecentsTask.equals(change.getContainer())) {
                         recentsOpening = change;
                     } else if (isLeafTask) {
@@ -240,8 +236,7 @@
                         }
                         openingTasks.add(change);
                     }
-                } else if (change.getMode() == TRANSIT_CLOSE
-                        || change.getMode() == TRANSIT_TO_BACK) {
+                } else if (TransitionUtil.isClosingType(change.getMode())) {
                     if (mRecentsTask.equals(change.getContainer())) {
                         foundRecentsClosing = true;
                     } else if (isLeafTask) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index eb4b05b..bd67115 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2843,7 +2843,8 @@
         final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
                 && mFaceSensorProperties.get(0).supportsFaceDetection
                 && canBypass && !mPrimaryBouncerIsOrWillBeShowing
-                && !isUserInLockdown(user);
+                && !isUserInLockdown(user)
+                && !isFingerprintLockedOut();
         final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
 
         // If the face or fp has recently been authenticated do not attempt to authenticate again.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index cef415c..98a3e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -130,7 +130,8 @@
         animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
 
         // For tablet docking animation, we don't play the background scrim.
-        if (!Utilities.isTablet(context)) {
+        // TODO(b/270524780): use utility to check for tablet instead. 
+        if (!Utilities.isLargeScreen(context)) {
             ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
                     "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
             scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f32ea71..ab78b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -155,7 +155,7 @@
     // TODO(b/255618149): Tracking Bug
     @JvmField
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
-        unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
+        releasedFlag(216, "customizable_lock_screen_quick_affordances")
 
     /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
     // TODO(b/256513609): Tracking Bug
@@ -187,7 +187,7 @@
 
     // TODO(b/262780002): Tracking Bug
     @JvmField
-    val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
+    val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
 
     /** A different path for unocclusion transitions back to keyguard */
     // TODO(b/262859270): Tracking Bug
@@ -214,10 +214,9 @@
     // TODO(b/266242192): Tracking Bug
     @JvmField
     val LOCK_SCREEN_LONG_PRESS_ENABLED =
-        unreleasedFlag(
+        releasedFlag(
             228,
-            "lock_screen_long_press_enabled",
-            teamfood = true,
+            "lock_screen_long_press_enabled"
         )
 
     // 300 - power menu
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 0ca9115..57c4b36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -945,9 +945,9 @@
             return false
         }
 
-        // We don't do the shared element on tablets because they're large and the smartspace has to
-        // fly across large distances, which is distracting.
-        if (Utilities.isTablet(context)) {
+        // We don't do the shared element on large screens because the smartspace has to fly across
+        // large distances, which is distracting.
+        if (Utilities.isLargeScreen(context)) {
             return false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index d4991f9..9b9d561 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
 
 /**
  * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
@@ -150,9 +150,9 @@
         val displayWidthPx = windowMetrics.bounds.width()
         val displayHeightPx = windowMetrics.bounds.height()
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
-        val isTablet = isTablet(context)
+        val isLargeScreen = isLargeScreen(context)
         val taskbarSize =
-            if (isTablet) {
+            if (isLargeScreen) {
                 resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
             } else {
                 0
@@ -166,7 +166,7 @@
             displayWidthPx,
             displayHeightPx,
             taskbarSize,
-            isTablet,
+            isLargeScreen,
             currentRotation,
             isRtl
         )
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 88d5eaa..1c90154 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -23,7 +23,7 @@
 import com.android.internal.R as AndroidR
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
-import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -61,8 +61,8 @@
         val width = windowMetrics.bounds.width()
         var height = maximumWindowHeight
 
-        val isTablet = isTablet(context)
-        if (isTablet) {
+        val isLargeScreen = isLargeScreen(context)
+        if (isLargeScreen) {
             val taskbarSize =
                 context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
             height -= taskbarSize
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index d5d7325..4db1da3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -41,7 +41,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -1792,7 +1792,7 @@
 
     private void setNavigationIconHints(int hints) {
         if (hints == mNavigationIconHints) return;
-        if (!isTablet(mContext)) {
+        if (!isLargeScreen(mContext)) {
             // All IME functions handled by launcher via Sysui flags for large screen
             final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
             final boolean oldBackAlt =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3c17465..63d977e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -21,7 +21,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 
 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
-import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -91,7 +91,7 @@
     private final DisplayManager mDisplayManager;
     private final TaskbarDelegate mTaskbarDelegate;
     private int mNavMode;
-    @VisibleForTesting boolean mIsTablet;
+    @VisibleForTesting boolean mIsLargeScreen;
 
     /** A displayId - nav bar maps. */
     @VisibleForTesting
@@ -138,16 +138,16 @@
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
                 dumpManager, autoHideController, lightBarController, pipOptional,
                 backAnimation.orElse(null), taskStackChangeListeners);
-        mIsTablet = isTablet(mContext);
+        mIsLargeScreen = isLargeScreen(mContext);
         dumpManager.registerDumpable(this);
     }
 
     @Override
     public void onConfigChanged(Configuration newConfig) {
-        boolean isOldConfigTablet = mIsTablet;
-        mIsTablet = isTablet(mContext);
+        boolean isOldConfigLargeScreen = mIsLargeScreen;
+        mIsLargeScreen = isLargeScreen(mContext);
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
-        boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+        boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
         Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
@@ -235,8 +235,9 @@
 
     /** @return {@code true} if taskbar is enabled, false otherwise */
     private boolean initializeTaskbarIfNecessary() {
-        // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet
-        boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW);
+        // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen
+        boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled(
+                Flags.HIDE_NAVBAR_WINDOW);
 
         if (taskbarEnabled) {
             Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
@@ -258,7 +259,7 @@
     @Override
     public void onDisplayReady(int displayId) {
         Display display = mDisplayManager.getDisplay(displayId);
-        mIsTablet = isTablet(mContext);
+        mIsLargeScreen = isLargeScreen(mContext);
         createNavigationBar(display, null /* savedState */, null /* result */);
     }
 
@@ -470,7 +471,7 @@
 
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mIsTablet=" + mIsTablet);
+        pw.println("mIsLargeScreen=" + mIsLargeScreen);
         pw.println("mNavMode=" + mNavMode);
         for (int i = 0; i < mNavigationBars.size(); i++) {
             if (i > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index be615d6..f7b7db4 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,15 +22,18 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.os.Build
 import android.os.UserManager
 import android.util.Log
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
 import java.util.Optional
+import java.util.concurrent.atomic.AtomicReference
 import javax.inject.Inject
 
 /**
@@ -41,18 +44,40 @@
  * Currently, we only support a single task per time.
  */
 @SysUISingleton
-internal class NoteTaskController
+class NoteTaskController
 @Inject
 constructor(
     private val context: Context,
     private val resolver: NoteTaskInfoResolver,
+    private val eventLogger: NoteTaskEventLogger,
     private val optionalBubbles: Optional<Bubbles>,
-    private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
+    private val optionalKeyguardManager: Optional<KeyguardManager>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
-    private val uiEventLogger: UiEventLogger,
 ) {
 
+    @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
+
+    /** @see BubbleExpandListener */
+    fun onBubbleExpandChanged(isExpanding: Boolean, key: String?) {
+        if (!isEnabled) return
+
+        if (key != Bubble.KEY_APP_BUBBLE) return
+
+        val info = infoReference.getAndSet(null)
+
+        // Safe guard mechanism, this callback should only be called for app bubbles.
+        if (info?.launchMode != NoteTaskLaunchMode.AppBubble) return
+
+        if (isExpanding) {
+            logDebug { "onBubbleExpandChanged - expanding: $info" }
+            eventLogger.logNoteTaskOpened(info)
+        } else {
+            logDebug { "onBubbleExpandChanged - collapsing: $info" }
+            eventLogger.logNoteTaskClosed(info)
+        }
+    }
+
     /**
      * Shows a note task. How the task is shown will depend on when the method is invoked.
      *
@@ -69,32 +94,50 @@
      * That will let users open other apps in full screen, and take contextual notes.
      */
     @JvmOverloads
-    fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) {
-
+    fun showNoteTask(
+        entryPoint: NoteTaskEntryPoint,
+        isInMultiWindowMode: Boolean = false,
+    ) {
         if (!isEnabled) return
 
         val bubbles = optionalBubbles.getOrNull() ?: return
-        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
+        val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
 
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        val noteTaskInfo = resolver.resolveInfo() ?: return
+        val info =
+            resolver.resolveInfo(
+                entryPoint = entryPoint,
+                isInMultiWindowMode = isInMultiWindowMode,
+                isKeyguardLocked = keyguardManager.isKeyguardLocked,
+            )
+                ?: return
 
-        uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
+        infoReference.set(info)
 
         // TODO(b/266686199): We should handle when app not available. For now, we log.
-        val intent = noteTaskInfo.toCreateNoteIntent()
+        val intent = createNoteIntent(info)
         try {
-            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
-                context.startActivity(intent)
-            } else {
-                bubbles.showOrHideAppBubble(intent)
+            logDebug { "onShowNoteTask - start: $info" }
+            when (info.launchMode) {
+                is NoteTaskLaunchMode.AppBubble -> {
+                    bubbles.showOrHideAppBubble(intent)
+                    // App bubble logging happens on `onBubbleExpandChanged`.
+                    logDebug { "onShowNoteTask - opened as app bubble: $info" }
+                }
+                is NoteTaskLaunchMode.Activity -> {
+                    context.startActivity(intent)
+                    eventLogger.logNoteTaskOpened(info)
+                    logDebug { "onShowNoteTask - opened as activity: $info" }
+                }
             }
+            logDebug { "onShowNoteTask - success: $info" }
         } catch (e: ActivityNotFoundException) {
-            Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e)
+            logDebug { "onShowNoteTask - failed: $info" }
         }
+        logDebug { "onShowNoteTask - compoleted: $info" }
     }
 
     /**
@@ -119,41 +162,12 @@
             enabledState,
             PackageManager.DONT_KILL_APP,
         )
-    }
 
-    /** IDs of UI events accepted by [showNoteTask]. */
-    enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
-        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
-
-        /* ktlint-disable max-line-length */
-        @UiEvent(
-            doc =
-                "User opened a note by pressing the stylus tail button while the screen was unlocked."
-        )
-        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
-        @UiEvent(
-            doc =
-                "User opened a note by pressing the stylus tail button while the screen was locked."
-        )
-        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
-        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
-        NOTE_OPENED_VIA_SHORTCUT(1297);
-
-        override fun getId() = _id
+        logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
     }
 
     companion object {
-        private val TAG = NoteTaskController::class.simpleName.orEmpty()
-
-        private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
-            return Intent(ACTION_CREATE_NOTE)
-                .setPackage(packageName)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
-                // was used to start it.
-                .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
-        }
+        val TAG = NoteTaskController::class.simpleName.orEmpty()
 
         // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
         const val NOTE_TASK_KEY_EVENT = 311
@@ -165,3 +179,17 @@
         const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
+
+private fun createNoteIntent(info: NoteTaskInfo): Intent =
+    Intent(NoteTaskController.ACTION_CREATE_NOTE)
+        .setPackage(info.packageName)
+        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
+        // was used to start it.
+        .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+
+private inline fun logDebug(message: () -> String) {
+    if (Build.IS_DEBUGGABLE) {
+        Log.d(NoteTaskController.TAG, message())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
index e0bf1da..a256391 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
@@ -19,4 +19,4 @@
 import javax.inject.Qualifier
 
 /** Key associated with a [Boolean] flag that enables or disables the note task feature. */
-@Qualifier internal annotation class NoteTaskEnabledKey
+@Qualifier annotation class NoteTaskEnabledKey
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
new file mode 100644
index 0000000..acc537a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.notetask
+
+import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceConfig
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import com.android.systemui.screenshot.AppClipsTrampolineActivity
+
+/**
+ * Supported entry points for [NoteTaskController.showNoteTask].
+ *
+ * An entry point represents where the note task has ben called from. In rare cases, it may
+ * represent a "re-entry" (i.e., [APP_CLIPS]).
+ */
+enum class NoteTaskEntryPoint {
+
+    /** @see [LaunchNoteTaskActivity] */
+    WIDGET_PICKER_SHORTCUT,
+
+    /** @see [NoteTaskQuickAffordanceConfig] */
+    QUICK_AFFORDANCE,
+
+    /** @see [NoteTaskInitializer.callbacks] */
+    TAIL_BUTTON,
+
+    /** @see [AppClipsTrampolineActivity] */
+    APP_CLIPS,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
new file mode 100644
index 0000000..16dd16e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.notetask
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+import javax.inject.Inject
+
+/**
+ * A wrapper around [UiEventLogger] specialized in the note taking UI events.
+ *
+ * if the accepted [NoteTaskInfo] contains a [NoteTaskInfo.entryPoint], it will be logged as the
+ * correct [NoteTaskUiEvent]. If null, it will be ignored.
+ *
+ * @see NoteTaskController for usage examples.
+ */
+class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
+
+    /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
+    fun logNoteTaskOpened(info: NoteTaskInfo) {
+        val event =
+            when (info.entryPoint) {
+                TAIL_BUTTON -> {
+                    if (info.isKeyguardLocked) {
+                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                    } else {
+                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+                    }
+                }
+                WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
+                QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+                APP_CLIPS -> return
+                null -> return
+            }
+        uiEventLogger.log(event, info.uid, info.packageName)
+    }
+
+    /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
+    fun logNoteTaskClosed(info: NoteTaskInfo) {
+        val event =
+            when (info.entryPoint) {
+                TAIL_BUTTON -> {
+                    if (info.isKeyguardLocked) {
+                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                    } else {
+                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+                    }
+                }
+                WIDGET_PICKER_SHORTCUT -> return
+                QUICK_AFFORDANCE -> return
+                APP_CLIPS -> return
+                null -> return
+            }
+        uiEventLogger.log(event, info.uid, info.packageName)
+    }
+
+    /** IDs of UI events accepted by [NoteTaskController]. */
+    enum class NoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+
+        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
+        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
+
+        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was unlocked.") // ktlint-disable max-line-length
+        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
+
+        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was locked.") // ktlint-disable max-line-length
+        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
+
+        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
+        NOTE_OPENED_VIA_SHORTCUT(1297),
+
+        @UiEvent(doc = "Note closed via a tail button while device is unlocked")
+        NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON(1311),
+
+        @UiEvent(doc = "Note closed via a tail button while device is locked")
+        NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1312);
+
+        override fun getId() = _id
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
new file mode 100644
index 0000000..28d7647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.notetask
+
+/** Contextual information required to launch a Note Task by [NoteTaskController]. */
+data class NoteTaskInfo(
+    val packageName: String,
+    val uid: Int,
+    val entryPoint: NoteTaskEntryPoint? = null,
+    val isInMultiWindowMode: Boolean = false,
+    val isKeyguardLocked: Boolean = false,
+) {
+
+    val launchMode: NoteTaskLaunchMode =
+        if (isInMultiWindowMode || isKeyguardLocked) {
+            NoteTaskLaunchMode.Activity
+        } else {
+            NoteTaskLaunchMode.AppBubble
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index bd822d4..b5d757c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -19,51 +19,56 @@
 import android.app.role.RoleManager
 import android.content.Context
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.os.UserHandle
 import android.util.Log
 import javax.inject.Inject
 
-internal class NoteTaskInfoResolver
+class NoteTaskInfoResolver
 @Inject
 constructor(
     private val context: Context,
     private val roleManager: RoleManager,
     private val packageManager: PackageManager,
 ) {
-    fun resolveInfo(): NoteTaskInfo? {
+    fun resolveInfo(
+        entryPoint: NoteTaskEntryPoint? = null,
+        isInMultiWindowMode: Boolean = false,
+        isKeyguardLocked: Boolean = false,
+    ): NoteTaskInfo? {
         // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
         val user = context.user
         val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull()
 
         if (packageName.isNullOrEmpty()) return null
 
-        return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user))
+        return NoteTaskInfo(
+            packageName = packageName,
+            uid = packageManager.getUidOf(packageName, user),
+            entryPoint = entryPoint,
+            isInMultiWindowMode = isInMultiWindowMode,
+            isKeyguardLocked = isKeyguardLocked,
+        )
     }
 
-    /** Package name and kernel user-ID of a note-taking app. */
-    data class NoteTaskInfo(val packageName: String, val uid: Int)
-
     companion object {
         private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
 
-        private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!!
+        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
+        const val ROLE_NOTES = "android.app.role.NOTES"
+
+        private val EMPTY_APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0)!!
 
         /**
          * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot
          * be found.
          */
-        private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int {
-            val applicationInfo =
-                try {
-                    getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user)
-                } catch (e: PackageManager.NameNotFoundException) {
-                    Log.e(TAG, "Couldn't find notes app UID", e)
-                    return 0
-                }
-            return applicationInfo.uid
-        }
-
-        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
-        const val ROLE_NOTES = "android.app.role.NOTES"
+        private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int =
+            try {
+                getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user).uid
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(TAG, "Couldn't find notes app UID", e)
+                0
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d40bf2b..3f4f8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -13,13 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui.notetask
 
-import android.app.KeyguardManager
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import javax.inject.Inject
@@ -28,11 +25,10 @@
 internal class NoteTaskInitializer
 @Inject
 constructor(
-    private val optionalBubbles: Optional<Bubbles>,
-    private val noteTaskController: NoteTaskController,
+    private val controller: NoteTaskController,
     private val commandQueue: CommandQueue,
+    private val optionalBubbles: Optional<Bubbles>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
-    private val optionalKeyguardManager: Optional<KeyguardManager>,
 ) {
 
     @VisibleForTesting
@@ -40,29 +36,17 @@
         object : CommandQueue.Callbacks {
             override fun handleSystemKey(keyCode: Int) {
                 if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) {
-                    showNoteTask()
+                    controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON)
                 }
             }
         }
 
-    private fun showNoteTask() {
-        val uiEvent =
-            if (optionalKeyguardManager.isKeyguardLocked) {
-                NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
-            } else {
-                NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
-            }
-        noteTaskController.showNoteTask(uiEvent = uiEvent)
-    }
-
     fun initialize() {
-        if (isEnabled && optionalBubbles.isPresent) {
-            commandQueue.addCallback(callbacks)
-        }
-        noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
+        controller.setNoteTaskShortcutEnabled(isEnabled)
+
+        // Guard against feature not being enabled or mandatory dependencies aren't available.
+        if (!isEnabled || optionalBubbles.isEmpty) return
+
+        commandQueue.addCallback(callbacks)
     }
 }
-
-private val Optional<KeyguardManager>.isKeyguardLocked: Boolean
-    // If there's no KeyguardManager, assume that the keyguard is not locked.
-    get() = getOrNull()?.isKeyguardLocked ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
new file mode 100644
index 0000000..836e103f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.notetask
+
+import android.content.Context
+import com.android.wm.shell.bubbles.Bubbles
+
+/**
+ * Supported ways for launching a note taking experience.
+ *
+ * @see [NoteTaskController.showNoteTask]
+ */
+sealed class NoteTaskLaunchMode {
+
+    /** @see Bubbles.showOrHideAppBubble */
+    object AppBubble : NoteTaskLaunchMode()
+
+    /** @see Context.startActivity */
+    object Activity : NoteTaskLaunchMode()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index b8800a2..f16110d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -36,7 +36,7 @@
 
 /** Compose all dependencies required by Note Task feature. */
 @Module(includes = [NoteTaskQuickAffordanceModule::class])
-internal interface NoteTaskModule {
+interface NoteTaskModule {
 
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
     fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 43869cc..30660c4 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -27,12 +27,12 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.notetask.NoteTaskEnabledKey
+import com.android.systemui.notetask.NoteTaskEntryPoint
 import javax.inject.Inject
 import kotlinx.coroutines.flow.flowOf
 
-internal class NoteTaskQuickAffordanceConfig
+class NoteTaskQuickAffordanceConfig
 @Inject
 constructor(
     context: Context,
@@ -66,7 +66,7 @@
 
     override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
         noteTaskController.showNoteTask(
-            uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+            entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE,
         )
         return OnTriggeredResult.Handled
     }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 7cb932a..2d63dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -22,7 +22,7 @@
 import dagger.multibindings.IntoSet
 
 @Module
-internal interface NoteTaskQuickAffordanceModule {
+interface NoteTaskQuickAffordanceModule {
 
     @[Binds IntoSet]
     fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index 6ab0da6..0a1d008 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -36,7 +36,7 @@
  * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
  * a custom shortcut activity</a>
  */
-internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 3ac5bfa..2b84bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -21,11 +21,11 @@
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
+import com.android.systemui.notetask.NoteTaskEntryPoint
 import javax.inject.Inject
 
 /** Activity responsible for launching the note experience, and finish. */
-internal class LaunchNoteTaskActivity
+class LaunchNoteTaskActivity
 @Inject
 constructor(
     private val noteTaskController: NoteTaskController,
@@ -35,8 +35,8 @@
         super.onCreate(savedInstanceState)
 
         noteTaskController.showNoteTask(
+            entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
             isInMultiWindowMode = isInMultiWindowMode,
-            uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
         )
 
         finish()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 645b125..346acf9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.recents;
 
-import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
@@ -265,7 +265,7 @@
                     .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
             View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
             if (!QuickStepContract.isGesturalMode(mNavBarMode)
-            	    && hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) {
+            	    && hasSoftNavigationBar(mContext.getDisplayId()) && !isLargeScreen(mContext)) {
                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
                 swapChildrenIfRtlAndVertical(buttons);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 1946b8e..eda38e4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -50,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.notetask.NoteTaskController;
+import com.android.systemui.notetask.NoteTaskEntryPoint;
 import com.android.systemui.settings.UserTracker;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -239,9 +240,8 @@
             // Broadcast no longer required, setting it to null.
             mKillAppClipsBroadcastIntent = null;
 
-            // Expand the note bubble before returning the result. As App Clips API is only
-            // available when in a bubble, isInMultiWindowMode is always false below.
-            mNoteTaskController.showNoteTask(false);
+            // Expand the note bubble before returning the result.
+            mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS);
             setResult(RESULT_OK, convertedData);
             finish();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
index e9fac28..1cfb400 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -39,7 +39,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (mIsShortcutListSearchEnabled && Utilities.isTablet(context)) {
+        if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(context)) {
             if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
                 KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */);
             } else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index f0b221d..0de3246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -344,7 +344,7 @@
             or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
 }
 
-class ChannelEditorDialog(context: Context) : Dialog(context) {
+class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) {
     fun updateDoneButtonText(hasChanges: Boolean) {
         findViewById<TextView>(R.id.done_button)?.setText(
                 if (hasChanges)
@@ -361,7 +361,7 @@
         }
 
         fun build(): ChannelEditorDialog {
-            return ChannelEditorDialog(context)
+            return ChannelEditorDialog(context, R.style.Theme_SystemUI_Dialog)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 17fb055..93b580c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,6 +164,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.OverlayPlugin;
@@ -642,7 +643,7 @@
     private NotificationActivityStarter mNotificationActivityStarter;
     private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private final Optional<Bubbles> mBubblesOptional;
-    private final Bubbles.BubbleExpandListener mBubbleExpandListener;
+    private final Lazy<NoteTaskController> mNoteTaskControllerLazy;
     private final Optional<StartingSurface> mStartingSurfaceOptional;
 
     private final ActivityIntentHelper mActivityIntentHelper;
@@ -705,6 +706,7 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             SysuiStatusBarStateController statusBarStateController,
             Optional<Bubbles> bubblesOptional,
+            Lazy<NoteTaskController> noteTaskControllerLazy,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             AccessibilityFloatingMenuController accessibilityFloatingMenuController,
@@ -795,6 +797,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mBubblesOptional = bubblesOptional;
+        mNoteTaskControllerLazy = noteTaskControllerLazy;
         mDeviceProvisionedController = deviceProvisionedController;
         mNavigationBarController = navigationBarController;
         mAccessibilityFloatingMenuController = accessibilityFloatingMenuController;
@@ -852,9 +855,6 @@
         mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
         mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
-        mBubbleExpandListener = (isExpanding, key) ->
-                mContext.getMainExecutor().execute(this::updateScrimController);
-
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
         mActivityLaunchAnimator = activityLaunchAnimator;
 
@@ -884,14 +884,21 @@
         }
     }
 
+    private void initBubbles(Bubbles bubbles) {
+        final Bubbles.BubbleExpandListener listener = (isExpanding, key) ->
+                mContext.getMainExecutor().execute(() -> {
+                    updateScrimController();
+                    mNoteTaskControllerLazy.get().onBubbleExpandChanged(isExpanding, key);
+                });
+        bubbles.setExpandListener(listener);
+    }
+
     @Override
     public void start() {
         mScreenLifecycle.addObserver(mScreenObserver);
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
-        if (mBubblesOptional.isPresent()) {
-            mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
-        }
+        mBubblesOptional.ifPresent(this::initBubbles);
 
         // Do not restart System UI when the bugreport flag changes.
         mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> {
@@ -2554,7 +2561,7 @@
             String action = intent.getAction();
             String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
-                if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) {
+                if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) {
                     KeyboardShortcutListSearch.dismiss();
                 } else {
                     KeyboardShortcuts.dismiss();
@@ -3905,7 +3912,7 @@
     }
 
     protected void toggleKeyboardShortcuts(int deviceId) {
-        if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) {
+        if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) {
             KeyboardShortcutListSearch.toggle(mContext, deviceId);
         } else {
             KeyboardShortcuts.toggle(mContext, deviceId);
@@ -3913,7 +3920,7 @@
     }
 
     protected void dismissKeyboardShortcuts() {
-        if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) {
+        if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) {
             KeyboardShortcutListSearch.dismiss();
         } else {
             KeyboardShortcuts.dismiss();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 395eb8f..09b738f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
@@ -835,6 +836,39 @@
     }
 
     @Test
+    public void noFaceRun_whenFpLockout() {
+        // GIVEN bypass is enabled, face detection is supported and strong auth is required
+        lockscreenBypassIsAllowed();
+        supportsFaceDetection();
+        strongAuthRequiredEncrypted();
+        keyguardIsVisible();
+        // fingerprint is NOT running, UDFPS is NOT supported
+
+        // GIVEN fp is locked out
+        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), anyInt()))
+                .thenReturn(BIOMETRIC_LOCKOUT_TIMED);
+        mKeyguardUpdateMonitor.handleUserSwitchComplete(0);
+        assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(true);
+
+        // WHEN the device wakes up
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+
+        // FACE detect is NOT triggered and face authenticate is NOT triggered
+        verifyFaceDetectNeverCalled();
+        verifyFaceAuthenticateNeverCalled();
+
+        // WHEN bouncer becomes visible
+        setKeyguardBouncerVisibility(true);
+        clearInvocations(mFaceManager);
+
+        // THEN face scanning is not run
+        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+        verifyFaceAuthenticateNeverCalled();
+        verifyFaceDetectNeverCalled();
+    }
+
+    @Test
     public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
         // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
         lockscreenBypassIsAllowed();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 2212bbd..89405c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -141,8 +141,8 @@
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultTrue() {
-        // Tablets may be using taskbar and the logic is different
-        mNavigationBarController.mIsTablet = false;
+        // Large screens may be using taskbar and the logic is different
+        mNavigationBarController.mIsLargeScreen = false;
         doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(true, null);
@@ -290,7 +290,7 @@
     @Test
     public void testConfigurationChange_taskbarNotInitialized() {
         Configuration configuration = mContext.getResources().getConfiguration();
-        when(Utilities.isTablet(any())).thenReturn(true);
+        when(Utilities.isLargeScreen(any())).thenReturn(true);
         mNavigationBarController.onConfigChanged(configuration);
         verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
     }
@@ -298,7 +298,7 @@
     @Test
     public void testConfigurationChange_taskbarInitialized() {
         Configuration configuration = mContext.getResources().getConfiguration();
-        when(Utilities.isTablet(any())).thenReturn(true);
+        when(Utilities.isLargeScreen(any())).thenReturn(true);
         when(mTaskbarDelegate.isInitialized()).thenReturn(true);
         mNavigationBarController.onConfigChanged(configuration);
         verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 39c4e06..0f0b7d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -23,16 +23,15 @@
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
-import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
@@ -44,12 +43,7 @@
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
-/**
- * Tests for [NoteTaskController].
- *
- * Build/Install/Run:
- * - atest SystemUITests:NoteTaskControllerTest
- */
+/** atest SystemUITests:NoteTaskControllerTest */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 internal class NoteTaskControllerTest : SysuiTestCase() {
@@ -58,46 +52,163 @@
     @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var resolver: NoteTaskInfoResolver
     @Mock lateinit var bubbles: Bubbles
-    @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var keyguardManager: KeyguardManager
-    @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
-    @Mock lateinit var optionalUserManager: Optional<UserManager>
     @Mock lateinit var userManager: UserManager
-    @Mock lateinit var uiEventLogger: UiEventLogger
+    @Mock lateinit var eventLogger: NoteTaskEventLogger
+
+    private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         whenever(context.packageManager).thenReturn(packageManager)
-        whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID))
-        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
-        whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
-        whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo)
         whenever(userManager.isUserUnlocked).thenReturn(true)
     }
 
-    private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
-        return NoteTaskController(
+    private fun createNoteTaskController(
+        isEnabled: Boolean = true,
+        bubbles: Bubbles? = this.bubbles,
+        keyguardManager: KeyguardManager? = this.keyguardManager,
+        userManager: UserManager? = this.userManager,
+    ): NoteTaskController =
+        NoteTaskController(
             context = context,
             resolver = resolver,
-            optionalBubbles = optionalBubbles,
-            optionalKeyguardManager = optionalKeyguardManager,
-            optionalUserManager = optionalUserManager,
+            eventLogger = eventLogger,
+            optionalBubbles = Optional.ofNullable(bubbles),
+            optionalUserManager = Optional.ofNullable(userManager),
+            optionalKeyguardManager = Optional.ofNullable(keyguardManager),
             isEnabled = isEnabled,
-            uiEventLogger = uiEventLogger,
         )
+
+    // region onBubbleExpandChanged
+    @Test
+    fun onBubbleExpandChanged_expanding_logNoteTaskOpened() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
     }
 
+    @Test
+    fun onBubbleExpandChanged_collapsing_logNoteTaskClosed() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = false,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verify(eventLogger).logNoteTaskClosed(expectedInfo)
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = false,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_expandingAndInMultiWindowMode_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_notExpandingAndInMultiWindowMode_doNothing() {
+        val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true)
+
+        createNoteTaskController()
+            .apply { infoReference.set(expectedInfo) }
+            .onBubbleExpandChanged(
+                isExpanding = false,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_notKeyAppBubble_shouldDoNothing() {
+        createNoteTaskController()
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = "any other key",
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+
+    @Test
+    fun onBubbleExpandChanged_flagDisabled_shouldDoNothing() {
+        createNoteTaskController(isEnabled = false)
+            .onBubbleExpandChanged(
+                isExpanding = true,
+                key = Bubble.KEY_APP_BUBBLE,
+            )
+
+        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+    }
+    // endregion
+
     // region showNoteTask
     @Test
     fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isInMultiWindowMode = false,
+                isKeyguardLocked = true,
+            )
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
 
         createNoteTaskController()
             .showNoteTask(
-                isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
+                entryPoint = expectedInfo.entryPoint!!,
+                isInMultiWindowMode = expectedInfo.isInMultiWindowMode,
             )
 
         val intentCaptor = argumentCaptor<Intent>()
@@ -108,23 +219,25 @@
             assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
         }
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
         verifyZeroInteractions(bubbles)
-        verify(uiEventLogger)
-            .log(
-                ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
-                NOTES_UID,
-                NOTES_PACKAGE_NAME
-            )
     }
 
     @Test
-    fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+    fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isInMultiWindowMode = false,
+                isKeyguardLocked = false,
+            )
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
 
         createNoteTaskController()
             .showNoteTask(
-                isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+                entryPoint = expectedInfo.entryPoint!!,
+                isInMultiWindowMode = expectedInfo.isInMultiWindowMode,
             )
 
         verifyZeroInteractions(context)
@@ -136,40 +249,24 @@
             assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
         }
-        verify(uiEventLogger)
-            .log(
-                ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
-                NOTES_UID,
-                NOTES_PACKAGE_NAME
-            )
-    }
-
-    @Test
-    fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
-
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
-        verifyZeroInteractions(context)
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
-        intentCaptor.value.let { intent ->
-            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
-            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
-            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
-            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
-        }
-        verifyZeroInteractions(uiEventLogger)
+        verifyZeroInteractions(eventLogger)
     }
 
     @Test
     fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
+                isInMultiWindowMode = true,
+                isKeyguardLocked = false,
+            )
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
 
         createNoteTaskController()
             .showNoteTask(
-                isInMultiWindowMode = true,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+                entryPoint = expectedInfo.entryPoint!!,
+                isInMultiWindowMode = expectedInfo.isInMultiWindowMode,
             )
 
         val intentCaptor = argumentCaptor<Intent>()
@@ -180,69 +277,65 @@
             assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
         }
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
         verifyZeroInteractions(bubbles)
-        verify(uiEventLogger)
-            .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME)
     }
 
     @Test
     fun showNoteTask_bubblesIsNull_shouldDoNothing() {
-        whenever(optionalBubbles.orElse(null)).thenReturn(null)
-
-        createNoteTaskController()
+        createNoteTaskController(bubbles = null)
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
-        whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
-
-        createNoteTaskController()
+        createNoteTaskController(keyguardManager = null)
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_userManagerIsNull_shouldDoNothing() {
-        whenever(optionalUserManager.orElse(null)).thenReturn(null)
-
-        createNoteTaskController()
+        createNoteTaskController(userManager = null)
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
-        whenever(resolver.resolveInfo()).thenReturn(null)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null)
 
         createNoteTaskController()
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_flagDisabled_shouldDoNothing() {
         createNoteTaskController(isEnabled = false)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
+            .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isInMultiWindowMode = false,
+            )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
     @Test
@@ -251,11 +344,11 @@
 
         createNoteTaskController()
             .showNoteTask(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
                 isInMultiWindowMode = false,
-                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
             )
 
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
+        verifyZeroInteractions(context, bubbles, eventLogger)
     }
     // endregion
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
new file mode 100644
index 0000000..a4df346
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+/** atest SystemUITests:MonitoringNoteTaskEventListenerTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskEventLoggerTest : SysuiTestCase() {
+
+    @Mock lateinit var uiEventLogger: UiEventLogger
+
+    private fun createNoteTaskEventLogger(): NoteTaskEventLogger =
+        NoteTaskEventLogger(uiEventLogger)
+
+    private fun createNoteTaskInfo(): NoteTaskInfo =
+        NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    // region logNoteTaskOpened
+    @Test
+    fun logNoteTaskOpened_entryPointWidgetPickerShortcut_noteOpenedViaShortcut() {
+        val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_SHORTCUT
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_entryPointQuickAffordance_noteOpenedViaQuickAffordance() {
+        val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardUnlocked_noteOpenedViaTailButtonUnlocked() { // ktlint-disable max-line-length
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = false,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardLocked_noteOpenedViaTailButtonLocked() { // ktlint-disable max-line-length
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = true,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun onNoteTaskBubbleExpanded_noEntryPoint_noLog() {
+        val info = createNoteTaskInfo().copy(entryPoint = null)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        verifyNoMoreInteractions(uiEventLogger)
+    }
+    // endregion
+
+    // region logNoteTaskClosed
+    @Test
+    fun logNoteTaskClosed_entryPointTailButton_noteClosedViaTailButtonUnlocked() {
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = false,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskClosed(info)
+
+        val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun logNoteTaskClosed_entryPointTailButtonAndKeyguardLocked_noteClosedViaTailButtonLocked() {
+        val info =
+            createNoteTaskInfo()
+                .copy(
+                    entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                    isKeyguardLocked = true,
+                )
+
+        createNoteTaskEventLogger().logNoteTaskClosed(info)
+
+        val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+        verify(uiEventLogger).log(expected, info.uid, info.packageName)
+    }
+
+    @Test
+    fun logNoteTaskClosed_noEntryPoint_noLog() {
+        val info = createNoteTaskInfo().copy(entryPoint = null)
+
+        createNoteTaskEventLogger().logNoteTaskOpened(info)
+
+        verifyNoMoreInteractions(uiEventLogger)
+    }
+    // endregion
+
+    private companion object {
+        const val NOTES_PACKAGE_NAME = "com.android.note.app"
+        const val NOTES_UID = 123456
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
new file mode 100644
index 0000000..7e975b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** atest SystemUITests:NoteTaskInfoTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInfoTest : SysuiTestCase() {
+
+    private fun createNoteTaskInfo(): NoteTaskInfo =
+        NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
+
+    @Test
+    fun launchMode_notInMultiWindowModeAndKeyguardUnlocked_launchModeAppBubble() {
+        val underTest =
+            createNoteTaskInfo()
+                .copy(
+                    isKeyguardLocked = false,
+                    isInMultiWindowMode = false,
+                )
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble)
+    }
+
+    @Test
+    fun launchMode_inMultiWindowMode_launchModeActivity() {
+        val underTest =
+            createNoteTaskInfo()
+                .copy(
+                    isKeyguardLocked = false,
+                    isInMultiWindowMode = true,
+                )
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+    }
+
+    @Test
+    fun launchMode_keyguardLocked_launchModeActivity() {
+        val underTest =
+            createNoteTaskInfo()
+                .copy(
+                    isKeyguardLocked = true,
+                    isInMultiWindowMode = false,
+                )
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+    }
+
+    private companion object {
+        const val NOTES_PACKAGE_NAME = "com.android.note.app"
+        const val NOTES_UID = 123456
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 53720ff..a8eab50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -15,15 +15,11 @@
  */
 package com.android.systemui.notetask
 
-import android.app.KeyguardManager
 import android.test.suitebuilder.annotation.SmallTest
 import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import org.junit.Before
@@ -48,27 +44,22 @@
 
     @Mock lateinit var commandQueue: CommandQueue
     @Mock lateinit var bubbles: Bubbles
-    @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var noteTaskController: NoteTaskController
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        whenever(optionalBubbles.isPresent).thenReturn(true)
-        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
     }
 
     private fun createNoteTaskInitializer(
         isEnabled: Boolean = true,
-        optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(),
+        bubbles: Bubbles? = this.bubbles,
     ): NoteTaskInitializer {
         return NoteTaskInitializer(
-            optionalBubbles = optionalBubbles,
-            noteTaskController = noteTaskController,
+            controller = noteTaskController,
             commandQueue = commandQueue,
+            optionalBubbles = Optional.ofNullable(bubbles),
             isEnabled = isEnabled,
-            optionalKeyguardManager = optionalKeyguardManager,
         )
     }
 
@@ -89,9 +80,7 @@
 
     @Test
     fun initialize_bubblesNotPresent_shouldDoNothing() {
-        whenever(optionalBubbles.isPresent).thenReturn(false)
-
-        createNoteTaskInitializer().initialize()
+        createNoteTaskInitializer(bubbles = null).initialize()
 
         verify(commandQueue, never()).addCallback(any())
     }
@@ -113,37 +102,12 @@
 
     // region handleSystemKey
     @Test
-    fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() {
-        val keyguardManager =
-            mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) }
-        createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
+    fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+        createNoteTaskInitializer()
             .callbacks
             .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
 
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
-    }
-
-    @Test
-    fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() {
-        val keyguardManager =
-            mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) }
-        createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
-            .callbacks
-            .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
-
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED)
-    }
-
-    @Test
-    fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() {
-        createNoteTaskInitializer(optionalKeyguardManager = Optional.empty())
-            .callbacks
-            .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
-
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
+        verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index cdc683f..e57d0d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
+import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -95,7 +95,6 @@
 
         underTest.onTriggered(expandable = null)
 
-        verify(noteTaskController)
-            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE)
+        verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
index bea2cfb..bedb2b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
@@ -73,7 +73,7 @@
                 .startMocking();
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
         mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
-        when(Utilities.isTablet(mContext)).thenReturn(true);
+        when(Utilities.isLargeScreen(mContext)).thenReturn(true);
 
         mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
 
@@ -90,7 +90,7 @@
                 .startMocking();
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
         mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
-        when(Utilities.isTablet(mContext)).thenReturn(false);
+        when(Utilities.isLargeScreen(mContext)).thenReturn(false);
 
         mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
 
@@ -107,7 +107,7 @@
                 .startMocking();
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
         mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
-        when(Utilities.isTablet(mContext)).thenReturn(true);
+        when(Utilities.isLargeScreen(mContext)).thenReturn(true);
 
         mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
 
@@ -124,7 +124,7 @@
                 .startMocking();
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true);
         mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
-        when(Utilities.isTablet(mContext)).thenReturn(false);
+        when(Utilities.isLargeScreen(mContext)).thenReturn(false);
 
         mKeyboardShortcutsReceiver.onReceive(mContext, mIntent);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 38e8228..db4bb45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -114,6 +114,7 @@
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
@@ -259,6 +260,7 @@
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private Bubbles mBubbles;
+    @Mock private NoteTaskController mNoteTaskController;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@@ -472,6 +474,7 @@
                 mWakefulnessLifecycle,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
+                () -> mNoteTaskController,
                 mDeviceProvisionedController,
                 mNavigationBarController,
                 mAccessibilityFloatingMenuController,
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 494c5a6..6a53adf 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -18,51 +18,31 @@
 
 import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManagerInternal;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Binder;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
-import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
-import com.android.server.companion.securechannel.SecureChannel;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-import libcore.util.EmptyArray;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicInteger;
 
 @SuppressLint("LongLogTag")
 public class CompanionTransportManager {
     private static final String TAG = "CDM_CompanionTransportManager";
-    // TODO: flip to false
-    private static final boolean DEBUG = true;
-
-    private static final int HEADER_LENGTH = 12;
-
-    private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
-    private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
-
-    private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
-    private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+    private static final boolean DEBUG = false;
 
     private boolean mSecureTransportEnabled = true;
 
@@ -127,9 +107,9 @@
 
             final Transport transport;
             if (isSecureTransportEnabled(associationId)) {
-                transport = new SecureTransport(associationId, fd);
+                transport = new SecureTransport(associationId, fd, mContext, mListener);
             } else {
-                transport = new RawTransport(associationId, fd);
+                transport = new RawTransport(associationId, fd, mContext, mListener);
             }
 
             transport.start();
@@ -172,296 +152,4 @@
         // TODO: version comparison logic
         return enabled;
     }
-
-    // TODO: Make Transport inner classes into standalone classes.
-    private abstract class Transport {
-        protected final int mAssociationId;
-        protected final InputStream mRemoteIn;
-        protected final OutputStream mRemoteOut;
-
-        @GuardedBy("mPendingRequests")
-        protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
-                new SparseArray<>();
-        protected final AtomicInteger mNextSequence = new AtomicInteger();
-
-        Transport(int associationId, ParcelFileDescriptor fd) {
-            this(associationId,
-                    new ParcelFileDescriptor.AutoCloseInputStream(fd),
-                    new ParcelFileDescriptor.AutoCloseOutputStream(fd));
-        }
-
-        Transport(int associationId, InputStream in, OutputStream out) {
-            this.mAssociationId = associationId;
-            this.mRemoteIn = in;
-            this.mRemoteOut = out;
-        }
-
-        public abstract void start();
-        public abstract void stop();
-
-        protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
-                throws IOException;
-
-        public Future<byte[]> requestForResponse(int message, byte[] data) {
-            if (DEBUG) Slog.d(TAG, "Requesting for response");
-            final int sequence = mNextSequence.incrementAndGet();
-            final CompletableFuture<byte[]> pending = new CompletableFuture<>();
-            synchronized (mPendingRequests) {
-                mPendingRequests.put(sequence, pending);
-            }
-
-            try {
-                sendMessage(message, sequence, data);
-            } catch (IOException e) {
-                synchronized (mPendingRequests) {
-                    mPendingRequests.remove(sequence);
-                }
-                pending.completeExceptionally(e);
-            }
-
-            return pending;
-        }
-
-        protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
-                throws IOException {
-            if (DEBUG) {
-                Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
-                        + " sequence " + sequence + " length " + data.length
-                        + " from association " + mAssociationId);
-            }
-
-            if (isRequest(message)) {
-                try {
-                    processRequest(message, sequence, data);
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed to respond to 0x" + Integer.toHexString(message), e);
-                }
-            } else if (isResponse(message)) {
-                processResponse(message, sequence, data);
-            } else {
-                Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message));
-            }
-        }
-
-        private void processRequest(int message, int sequence, byte[] data)
-                throws IOException {
-            switch (message) {
-                case MESSAGE_REQUEST_PING: {
-                    sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
-                    break;
-                }
-                case MESSAGE_REQUEST_PERMISSION_RESTORE: {
-                    if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
-                            && !Build.isDebuggable()) {
-                        Slog.w(TAG, "Restoring permissions only supported on watches");
-                        sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
-                        break;
-                    }
-                    try {
-                        mListener.onRequestPermissionRestore(data);
-                        sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
-                    } catch (Exception e) {
-                        Slog.w(TAG, "Failed to restore permissions");
-                        sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
-                    }
-                    break;
-                }
-                default: {
-                    Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message));
-                    sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
-                    break;
-                }
-            }
-        }
-
-        private void processResponse(int message, int sequence, byte[] data) {
-            final CompletableFuture<byte[]> future;
-            synchronized (mPendingRequests) {
-                future = mPendingRequests.removeReturnOld(sequence);
-            }
-            if (future == null) {
-                Slog.w(TAG, "Ignoring unknown sequence " + sequence);
-                return;
-            }
-
-            switch (message) {
-                case MESSAGE_RESPONSE_SUCCESS: {
-                    future.complete(data);
-                    break;
-                }
-                case MESSAGE_RESPONSE_FAILURE: {
-                    future.completeExceptionally(new RuntimeException("Remote failure"));
-                    break;
-                }
-                default: {
-                    Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message));
-                }
-            }
-        }
-    }
-
-    private class RawTransport extends Transport {
-        private volatile boolean mStopped;
-
-        RawTransport(int associationId, ParcelFileDescriptor fd) {
-            super(associationId, fd);
-        }
-
-        @Override
-        public void start() {
-            new Thread(() -> {
-                try {
-                    while (!mStopped) {
-                        receiveMessage();
-                    }
-                } catch (IOException e) {
-                    if (!mStopped) {
-                        Slog.w(TAG, "Trouble during transport", e);
-                        stop();
-                    }
-                }
-            }).start();
-        }
-
-        @Override
-        public void stop() {
-            mStopped = true;
-
-            IoUtils.closeQuietly(mRemoteIn);
-            IoUtils.closeQuietly(mRemoteOut);
-        }
-
-        @Override
-        protected void sendMessage(int message, int sequence, @NonNull byte[] data)
-                throws IOException {
-            if (DEBUG) {
-                Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
-                        + " sequence " + sequence + " length " + data.length
-                        + " to association " + mAssociationId);
-            }
-
-            synchronized (mRemoteOut) {
-                final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
-                        .putInt(message)
-                        .putInt(sequence)
-                        .putInt(data.length);
-                mRemoteOut.write(header.array());
-                mRemoteOut.write(data);
-                mRemoteOut.flush();
-            }
-        }
-
-        private void receiveMessage() throws IOException {
-            final byte[] headerBytes = new byte[HEADER_LENGTH];
-            Streams.readFully(mRemoteIn, headerBytes);
-            final ByteBuffer header = ByteBuffer.wrap(headerBytes);
-            final int message = header.getInt();
-            final int sequence = header.getInt();
-            final int length = header.getInt();
-            final byte[] data = new byte[length];
-            Streams.readFully(mRemoteIn, data);
-
-            handleMessage(message, sequence, data);
-        }
-    }
-
-    private class SecureTransport extends Transport implements SecureChannel.Callback {
-        private final SecureChannel mSecureChannel;
-
-        private volatile boolean mShouldProcessRequests = false;
-
-        private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
-
-        SecureTransport(int associationId, ParcelFileDescriptor fd) {
-            super(associationId, fd);
-            mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, mContext);
-        }
-
-        @Override
-        public void start() {
-            mSecureChannel.start();
-        }
-
-        @Override
-        public void stop() {
-            mSecureChannel.stop();
-            mShouldProcessRequests = false;
-        }
-
-        @Override
-        public Future<byte[]> requestForResponse(int message, byte[] data) {
-            // Check if channel is secured and start securing
-            if (!mShouldProcessRequests) {
-                Slog.d(TAG, "Establishing secure connection.");
-                try {
-                    mSecureChannel.establishSecureConnection();
-                } catch (Exception e) {
-                    Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
-                    onError(e);
-                }
-            }
-
-            return super.requestForResponse(message, data);
-        }
-
-        @Override
-        protected void sendMessage(int message, int sequence, @NonNull byte[] data)
-                throws IOException {
-            if (DEBUG) {
-                Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
-                        + " sequence " + sequence + " length " + data.length
-                        + " to association " + mAssociationId);
-            }
-
-            // Queue up a message to send
-            mRequestQueue.add(ByteBuffer.allocate(HEADER_LENGTH + data.length)
-                    .putInt(message)
-                    .putInt(sequence)
-                    .putInt(data.length)
-                    .put(data)
-                    .array());
-        }
-
-        @Override
-        public void onSecureConnection() {
-            mShouldProcessRequests = true;
-            Slog.d(TAG, "Secure connection established.");
-
-            // TODO: find a better way to handle incoming requests than a dedicated thread.
-            new Thread(() -> {
-                try {
-                    while (mShouldProcessRequests) {
-                        byte[] request = mRequestQueue.poll();
-                        if (request != null) {
-                            mSecureChannel.sendSecureMessage(request);
-                        }
-                    }
-                } catch (IOException e) {
-                    onError(e);
-                }
-            }).start();
-        }
-
-        @Override
-        public void onSecureMessageReceived(byte[] data) {
-            final ByteBuffer payload = ByteBuffer.wrap(data);
-            final int message = payload.getInt();
-            final int sequence = payload.getInt();
-            final int length = payload.getInt();
-            final byte[] content = new byte[length];
-            payload.get(content);
-
-            try {
-                handleMessage(message, sequence, content);
-            } catch (IOException error) {
-                onError(error);
-            }
-        }
-
-        @Override
-        public void onError(Throwable error) {
-            mShouldProcessRequests = false;
-            Slog.e(TAG, error.getMessage(), error);
-        }
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
new file mode 100644
index 0000000..7c0c7cf
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.transport;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+
+import com.android.server.companion.transport.CompanionTransportManager.Listener;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+class RawTransport extends Transport {
+    private volatile boolean mStopped;
+
+    RawTransport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) {
+        super(associationId, fd, context, listener);
+    }
+
+    @Override
+    public void start() {
+        new Thread(() -> {
+            try {
+                while (!mStopped) {
+                    receiveMessage();
+                }
+            } catch (IOException e) {
+                if (!mStopped) {
+                    Slog.w(TAG, "Trouble during transport", e);
+                    stop();
+                }
+            }
+        }).start();
+    }
+
+    @Override
+    public void stop() {
+        mStopped = true;
+
+        IoUtils.closeQuietly(mRemoteIn);
+        IoUtils.closeQuietly(mRemoteOut);
+    }
+
+    @Override
+    protected void sendMessage(int message, int sequence, @NonNull byte[] data)
+            throws IOException {
+        if (DEBUG) {
+            Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+                    + " sequence " + sequence + " length " + data.length
+                    + " to association " + mAssociationId);
+        }
+
+        synchronized (mRemoteOut) {
+            final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
+                    .putInt(message)
+                    .putInt(sequence)
+                    .putInt(data.length);
+            mRemoteOut.write(header.array());
+            mRemoteOut.write(data);
+            mRemoteOut.flush();
+        }
+    }
+
+    private void receiveMessage() throws IOException {
+        final byte[] headerBytes = new byte[HEADER_LENGTH];
+        Streams.readFully(mRemoteIn, headerBytes);
+        final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+        final int message = header.getInt();
+        final int sequence = header.getInt();
+        final int length = header.getInt();
+        final byte[] data = new byte[length];
+        Streams.readFully(mRemoteIn, data);
+
+        handleMessage(message, sequence, data);
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
new file mode 100644
index 0000000..4194130
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.transport;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+
+import com.android.server.companion.securechannel.SecureChannel;
+import com.android.server.companion.transport.CompanionTransportManager.Listener;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Future;
+
+class SecureTransport extends Transport implements SecureChannel.Callback {
+    private final SecureChannel mSecureChannel;
+
+    private volatile boolean mShouldProcessRequests = false;
+
+    private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
+
+    SecureTransport(int associationId,
+            ParcelFileDescriptor fd,
+            Context context,
+            Listener listener) {
+        super(associationId, fd, context, listener);
+        mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context);
+    }
+
+    @Override
+    public void start() {
+        mSecureChannel.start();
+    }
+
+    @Override
+    public void stop() {
+        mSecureChannel.stop();
+        mShouldProcessRequests = false;
+    }
+
+    @Override
+    public Future<byte[]> requestForResponse(int message, byte[] data) {
+        // Check if channel is secured and start securing
+        if (!mShouldProcessRequests) {
+            Slog.d(TAG, "Establishing secure connection.");
+            try {
+                mSecureChannel.establishSecureConnection();
+            } catch (Exception e) {
+                Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
+                onError(e);
+            }
+        }
+
+        return super.requestForResponse(message, data);
+    }
+
+    @Override
+    protected void sendMessage(int message, int sequence, @NonNull byte[] data)
+            throws IOException {
+        if (DEBUG) {
+            Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
+                    + " sequence " + sequence + " length " + data.length
+                    + " to association " + mAssociationId);
+        }
+
+        // Queue up a message to send
+        mRequestQueue.add(ByteBuffer.allocate(HEADER_LENGTH + data.length)
+                .putInt(message)
+                .putInt(sequence)
+                .putInt(data.length)
+                .put(data)
+                .array());
+    }
+
+    @Override
+    public void onSecureConnection() {
+        mShouldProcessRequests = true;
+        Slog.d(TAG, "Secure connection established.");
+
+        // TODO: find a better way to handle incoming requests than a dedicated thread.
+        new Thread(() -> {
+            try {
+                while (mShouldProcessRequests) {
+                    byte[] request = mRequestQueue.poll();
+                    if (request != null) {
+                        mSecureChannel.sendSecureMessage(request);
+                    }
+                }
+            } catch (IOException e) {
+                onError(e);
+            }
+        }).start();
+    }
+
+    @Override
+    public void onSecureMessageReceived(byte[] data) {
+        final ByteBuffer payload = ByteBuffer.wrap(data);
+        final int message = payload.getInt();
+        final int sequence = payload.getInt();
+        final int length = payload.getInt();
+        final byte[] content = new byte[length];
+        payload.get(content);
+
+        try {
+            handleMessage(message, sequence, content);
+        } catch (IOException error) {
+            onError(error);
+        }
+    }
+
+    @Override
+    public void onError(Throwable error) {
+        mShouldProcessRequests = false;
+        Slog.e(TAG, error.getMessage(), error);
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
new file mode 100644
index 0000000..923d424
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.transport;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.transport.CompanionTransportManager.Listener;
+
+import libcore.util.EmptyArray;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+abstract class Transport {
+    protected static final String TAG = "CDM_CompanionTransport";
+    protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
+
+    static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+    static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+
+    static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
+    static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+
+    protected static final int HEADER_LENGTH = 12;
+
+    protected final int mAssociationId;
+    protected final InputStream mRemoteIn;
+    protected final OutputStream mRemoteOut;
+    protected final Context mContext;
+
+    private final Listener mListener;
+
+    private static boolean isRequest(int message) {
+        return (message & 0xFF000000) == 0x63000000;
+    }
+
+    private static boolean isResponse(int message) {
+        return (message & 0xFF000000) == 0x33000000;
+    }
+
+    @GuardedBy("mPendingRequests")
+    protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
+            new SparseArray<>();
+    protected final AtomicInteger mNextSequence = new AtomicInteger();
+
+    Transport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) {
+        this.mAssociationId = associationId;
+        this.mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+        this.mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+        this.mContext = context;
+        this.mListener = listener;
+    }
+
+    public abstract void start();
+    public abstract void stop();
+
+    public Future<byte[]> requestForResponse(int message, byte[] data) {
+        if (DEBUG) Slog.d(TAG, "Requesting for response");
+        final int sequence = mNextSequence.incrementAndGet();
+        final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+        synchronized (mPendingRequests) {
+            mPendingRequests.put(sequence, pending);
+        }
+
+        try {
+            sendMessage(message, sequence, data);
+        } catch (IOException e) {
+            synchronized (mPendingRequests) {
+                mPendingRequests.remove(sequence);
+            }
+            pending.completeExceptionally(e);
+        }
+
+        return pending;
+    }
+
+    protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
+            throws IOException;
+
+    protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
+            throws IOException {
+        if (DEBUG) {
+            Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
+                    + " sequence " + sequence + " length " + data.length
+                    + " from association " + mAssociationId);
+        }
+
+        if (isRequest(message)) {
+            try {
+                processRequest(message, sequence, data);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to respond to 0x" + Integer.toHexString(message), e);
+            }
+        } else if (isResponse(message)) {
+            processResponse(message, sequence, data);
+        } else {
+            Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message));
+        }
+    }
+
+    private void processRequest(int message, int sequence, byte[] data)
+            throws IOException {
+        switch (message) {
+            case MESSAGE_REQUEST_PING: {
+                sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
+                break;
+            }
+            case MESSAGE_REQUEST_PERMISSION_RESTORE: {
+                if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                        && !Build.isDebuggable()) {
+                    Slog.w(TAG, "Restoring permissions only supported on watches");
+                    sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+                    break;
+                }
+                try {
+                    mListener.onRequestPermissionRestore(data);
+                    sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to restore permissions");
+                    sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+                }
+                break;
+            }
+            default: {
+                Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message));
+                sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+                break;
+            }
+        }
+    }
+
+    private void processResponse(int message, int sequence, byte[] data) {
+        final CompletableFuture<byte[]> future;
+        synchronized (mPendingRequests) {
+            future = mPendingRequests.removeReturnOld(sequence);
+        }
+        if (future == null) {
+            Slog.w(TAG, "Ignoring unknown sequence " + sequence);
+            return;
+        }
+
+        switch (message) {
+            case MESSAGE_RESPONSE_SUCCESS: {
+                future.complete(data);
+                break;
+            }
+            case MESSAGE_RESPONSE_FAILURE: {
+                future.completeExceptionally(new RuntimeException("Remote failure"));
+                break;
+            }
+            default: {
+                Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message));
+            }
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b4dcf43..f650560 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -51,6 +51,10 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualKeyboardConfig;
@@ -82,6 +86,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.LocalServices;
 import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
 import com.android.server.companion.virtual.audio.VirtualAudioController;
 
@@ -109,21 +114,29 @@
 
     private final Context mContext;
     private final AssociationInfo mAssociationInfo;
+    private final VirtualDeviceManagerService mService;
     private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
     private final int mDeviceId;
+    // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
+    // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
+    // 1. After display is created the window manager calls into VDM during construction
+    //   of display specific context to fetch device id corresponding to the display.
+    //   mVirtualDeviceLock will be held while this is done.
+    // 2. InputController interactions result in calls to DisplayManager (to set IME,
+    //    possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
+    //    creates lock inversion.
     private final InputController mInputController;
     private final SensorController mSensorController;
     private final CameraAccessController mCameraAccessController;
     private VirtualAudioController mVirtualAudioController;
-    @VisibleForTesting
-    final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>();
-    private final OnDeviceCloseListener mOnDeviceCloseListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
-    private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
+    @GuardedBy("mVirtualDeviceLock")
+    private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
     private final IVirtualDeviceActivityListener mActivityListener;
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+    private final DisplayManagerGlobal mDisplayManager;
     @GuardedBy("mVirtualDeviceLock")
     private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
     @NonNull
@@ -174,21 +187,14 @@
         };
     }
 
-    /**
-     * A mapping from the virtual display ID to its corresponding
-     * {@link GenericWindowPolicyController}.
-     */
-    private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
-            new SparseArray<>();
-
     VirtualDeviceImpl(
             Context context,
             AssociationInfo associationInfo,
+            VirtualDeviceManagerService service,
             IBinder token,
             int ownerUid,
             int deviceId,
             CameraAccessController cameraAccessController,
-            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             IVirtualDeviceSoundEffectListener soundEffectListener,
@@ -197,40 +203,43 @@
         this(
                 context,
                 associationInfo,
+                service,
                 token,
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
                 /* sensorController= */ null,
                 cameraAccessController,
-                onDeviceCloseListener,
                 pendingTrampolineCallback,
                 activityListener,
                 soundEffectListener,
                 runningAppsChangedCallback,
-                params);
+                params,
+                DisplayManagerGlobal.getInstance());
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(
             Context context,
             AssociationInfo associationInfo,
+            VirtualDeviceManagerService service,
             IBinder token,
             int ownerUid,
             int deviceId,
             InputController inputController,
             SensorController sensorController,
             CameraAccessController cameraAccessController,
-            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             IVirtualDeviceSoundEffectListener soundEffectListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
-            VirtualDeviceParams params) {
+            VirtualDeviceParams params,
+            DisplayManagerGlobal displayManager) {
         super(PermissionEnforcer.fromContext(context));
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
+        mService = service;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
         mSoundEffectListener = soundEffectListener;
@@ -239,6 +248,7 @@
         mDeviceId = deviceId;
         mAppToken = token;
         mParams = params;
+        mDisplayManager = displayManager;
         if (inputController == null) {
             mInputController = new InputController(
                     mVirtualDeviceLock,
@@ -259,7 +269,6 @@
         }
         mCameraAccessController = cameraAccessController;
         mCameraAccessController.startObservingIfNeeded();
-        mOnDeviceCloseListener = onDeviceCloseListener;
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -331,9 +340,11 @@
     @Override // Binder call
     public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
             ResultReceiver resultReceiver) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            throw new SecurityException("Display ID " + displayId
-                    + " not found for this virtual device");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                throw new SecurityException("Display ID " + displayId
+                        + " not found for this virtual device");
+            }
         }
         if (pendingIntent.isActivity()) {
             try {
@@ -383,24 +394,34 @@
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
         super.close_enforcePermission();
+        // Remove about-to-be-closed virtual device from the service before butchering it.
+        mService.removeVirtualDevice(mDeviceId);
+
+        VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
         synchronized (mVirtualDeviceLock) {
-            if (!mPerDisplayWakelocks.isEmpty()) {
-                mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
-                    Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
-                            + " was not properly released");
-                    wakeLock.release();
-                });
-                mPerDisplayWakelocks.clear();
-            }
             if (mVirtualAudioController != null) {
                 mVirtualAudioController.stopListening();
                 mVirtualAudioController = null;
             }
             mLocaleList = null;
+            virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
+            }
+            mVirtualDisplays.clear();
             mVirtualSensorList = null;
             mVirtualSensors.clear();
         }
-        mOnDeviceCloseListener.onClose(mDeviceId);
+        // Destroy the display outside locked section.
+        for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
+            mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
+            // The releaseVirtualDisplay call above won't trigger
+            // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
+            // virtual device from the service - we release the other display-tied resources here
+            // with the guarantee it will be done exactly once.
+            releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+        }
+
         mAppToken.unlinkToDeath(this, 0);
         mCameraAccessController.stopObservingIfNeeded();
 
@@ -429,11 +450,6 @@
         return mVirtualAudioController;
     }
 
-    @VisibleForTesting
-    SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() {
-        return mWindowPolicyControllers;
-    }
-
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionStarting(int displayId,
@@ -441,7 +457,7 @@
             @Nullable IAudioConfigChangedCallback configChangedCallback) {
         super.onAudioSessionStarting_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplays.contains(displayId)) {
                 throw new SecurityException(
                         "Cannot start audio session for a display not associated with this virtual "
                                 + "device");
@@ -449,7 +465,8 @@
 
             if (mVirtualAudioController == null) {
                 mVirtualAudioController = new VirtualAudioController(mContext);
-                GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+                GenericWindowPolicyController gwpc = mVirtualDisplays.get(
+                        displayId).getWindowPolicyController();
                 mVirtualAudioController.startListening(gwpc, routingCallback,
                         configChangedCallback);
             }
@@ -473,7 +490,7 @@
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualDpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual dpad for a display not associated with "
                                 + "this virtual device");
@@ -493,7 +510,7 @@
     public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualKeyboard_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual keyboard for a display not associated with "
                                 + "this virtual device");
@@ -515,7 +532,7 @@
     public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualMouse_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual mouse for a display not associated with this "
                                 + "virtual device");
@@ -536,7 +553,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualTouchscreen_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual touchscreen for a display not associated with "
                                 + "this virtual device");
@@ -566,7 +583,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualNavigationTouchpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual navigation touchpad for a display not associated "
                                 + "with this virtual device");
@@ -704,7 +721,8 @@
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
-                for (int displayId : mVirtualDisplayIds) {
+                for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                    final int displayId = mVirtualDisplays.keyAt(i);
                     mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
                 }
             }
@@ -795,8 +813,8 @@
         fout.println("    mParams: " + mParams);
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
-            for (int id : mVirtualDisplayIds) {
-                fout.println("      " + id);
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                fout.println("      " + mVirtualDisplays.keyAt(i));
             }
             fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
@@ -804,61 +822,75 @@
         mSensorController.dump(fout);
     }
 
-    GenericWindowPolicyController createWindowPolicyController(
+    private GenericWindowPolicyController createWindowPolicyController(
             @NonNull List<String> displayCategories) {
-        synchronized (mVirtualDeviceLock) {
-            final GenericWindowPolicyController gwpc =
-                    new GenericWindowPolicyController(FLAG_SECURE,
-                            SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
-                            getAllowedUserHandles(),
-                            mParams.getAllowedCrossTaskNavigations(),
-                            mParams.getBlockedCrossTaskNavigations(),
-                            mParams.getAllowedActivities(),
-                            mParams.getBlockedActivities(),
-                            mParams.getDefaultActivityPolicy(),
-                            createListenerAdapter(),
-                            this::onEnteringPipBlocked,
-                            this::onActivityBlocked,
-                            this::onSecureWindowShown,
-                            this::shouldInterceptIntent,
-                            displayCategories,
-                            mParams.getDefaultRecentsPolicy());
-            gwpc.registerRunningAppsChangedListener(/* listener= */ this);
-            return gwpc;
-        }
+        final GenericWindowPolicyController gwpc =
+                new GenericWindowPolicyController(FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        getAllowedUserHandles(),
+                        mParams.getAllowedCrossTaskNavigations(),
+                        mParams.getBlockedCrossTaskNavigations(),
+                        mParams.getAllowedActivities(),
+                        mParams.getBlockedActivities(),
+                        mParams.getDefaultActivityPolicy(),
+                        createListenerAdapter(),
+                        this::onEnteringPipBlocked,
+                        this::onActivityBlocked,
+                        this::onSecureWindowShown,
+                        this::shouldInterceptIntent,
+                        displayCategories,
+                        mParams.getDefaultRecentsPolicy());
+        gwpc.registerRunningAppsChangedListener(/* listener= */ this);
+        return gwpc;
     }
 
-    void onVirtualDisplayCreatedLocked(GenericWindowPolicyController gwpc, int displayId) {
+    int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @NonNull IVirtualDisplayCallback callback, String packageName) {
+        GenericWindowPolicyController gwpc = createWindowPolicyController(
+                virtualDisplayConfig.getDisplayCategories());
+        DisplayManagerInternal displayManager = LocalServices.getService(
+                DisplayManagerInternal.class);
+        int displayId;
+        displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
+                this, gwpc, packageName);
+        gwpc.setDisplayId(displayId);
+
         synchronized (mVirtualDeviceLock) {
-            if (displayId == Display.INVALID_DISPLAY) {
-                return;
-            }
-            if (mVirtualDisplayIds.contains(displayId)) {
+            if (mVirtualDisplays.contains(displayId)) {
+                gwpc.unregisterRunningAppsChangedListener(this);
                 throw new IllegalStateException(
                         "Virtual device already has a virtual display with ID " + displayId);
             }
-            mVirtualDisplayIds.add(displayId);
 
-            gwpc.setDisplayId(displayId);
-            mWindowPolicyControllers.put(displayId, gwpc);
+            PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
+            mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+        }
 
+        final long token = Binder.clearCallingIdentity();
+        try {
             mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
             mInputController.setPointerAcceleration(1f, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
             mInputController.setLocalIme(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
 
+        return displayId;
+    }
 
-            if (mPerDisplayWakelocks.containsKey(displayId)) {
-                Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
-                return;
-            }
+    private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+        final long token = Binder.clearCallingIdentity();
+        try {
             PowerManager powerManager = mContext.getSystemService(PowerManager.class);
             PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
                     PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
                     TAG + ":" + displayId, displayId);
-            mPerDisplayWakelocks.put(displayId, wakeLock);
             wakeLock.acquire();
+            return wakeLock;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -872,8 +904,10 @@
     }
 
     private void onSecureWindowShown(int displayId, int uid) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            return;
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                return;
+            }
         }
 
         // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
@@ -888,55 +922,102 @@
 
     private ArraySet<UserHandle> getAllowedUserHandles() {
         ArraySet<UserHandle> result = new ArraySet<>();
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        UserManager userManager = mContext.getSystemService(UserManager.class);
-        for (UserHandle profile : userManager.getAllProfiles()) {
-            int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
-            if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
-                    || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
-                result.add(profile);
-            } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
-                if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            for (UserHandle profile : userManager.getAllProfiles()) {
+                int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(
+                        profile.getIdentifier());
+                if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+                        || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
                     result.add(profile);
+                } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+                    if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+                        result.add(profile);
+                    }
                 }
             }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
         return result;
     }
 
-    void onVirtualDisplayRemovedLocked(int displayId) {
+
+    void onVirtualDisplayRemoved(int displayId) {
+        /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
+         * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
+         * At this point, the display is already released, but we still need to release the
+         * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
+         * WindowPolicyController.
+         *
+         * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
+         * this callback won't be invoked because the display is removed from
+         * VirtualDeviceManagerService before any resources are released.
+         */
+        VirtualDisplayWrapper virtualDisplayWrapper;
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
-                throw new IllegalStateException(
-                        "Virtual device doesn't have a virtual display with ID " + displayId);
-            }
-            PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
-            if (wakeLock != null) {
-                wakeLock.release();
-                mPerDisplayWakelocks.remove(displayId);
-            }
-            GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
-            if (gwpc != null) {
-                gwpc.unregisterRunningAppsChangedListener(/* listener= */ this);
-            }
-            mVirtualDisplayIds.remove(displayId);
-            mWindowPolicyControllers.remove(displayId);
+            virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
         }
+
+        if (virtualDisplayWrapper == null) {
+            throw new IllegalStateException(
+                    "Virtual device doesn't have a virtual display with ID " + displayId);
+        }
+
+        releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+
+    }
+
+    /**
+     * Release resources tied to virtual display owned by this VirtualDevice instance.
+     *
+     * Note that this method won't release the virtual display itself.
+     *
+     * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
+     */
+    private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
+        virtualDisplayWrapper.getWakeLock().release();
+        virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
+                this);
     }
 
     int getOwnerUid() {
         return mOwnerUid;
     }
 
+    ArraySet<Integer> getDisplayIds() {
+        synchronized (mVirtualDeviceLock) {
+            final int size = mVirtualDisplays.size();
+            ArraySet<Integer> arraySet = new ArraySet<>(size);
+            for (int i = 0; i < size; i++) {
+                arraySet.append(mVirtualDisplays.keyAt(i));
+            }
+            return arraySet;
+        }
+    }
+
+    @VisibleForTesting
+    GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) {
+        VirtualDisplayWrapper virtualDisplayWrapper;
+        synchronized (mVirtualDeviceLock) {
+            virtualDisplayWrapper = mVirtualDisplays.get(displayId);
+        }
+        return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController()
+                : null;
+    }
+
     /**
      * Returns true if an app with the given {@code uid} is currently running on this virtual
      * device.
      */
     boolean isAppRunningOnVirtualDevice(int uid) {
-        final int size = mWindowPolicyControllers.size();
-        for (int i = 0; i < size; i++) {
-            if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
-                return true;
+        synchronized (mVirtualDeviceLock) {
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+                    return true;
+                }
             }
         }
         return false;
@@ -957,11 +1038,9 @@
             Looper looper) {
         synchronized (mVirtualDeviceLock) {
             DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-            final int size = mWindowPolicyControllers.size();
-            for (int i = 0; i < size; i++) {
-                if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
-                    int displayId = mWindowPolicyControllers.keyAt(i);
-                    Display display = displayManager.getDisplay(displayId);
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+                    Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
                     if (display != null && display.isValid()) {
                         Toast.makeText(mContext.createDisplayContext(display), looper, text,
                                 duration).show();
@@ -972,7 +1051,9 @@
     }
 
     boolean isDisplayOwnedByVirtualDevice(int displayId) {
-        return mVirtualDisplayIds.contains(displayId);
+        synchronized (mVirtualDeviceLock) {
+            return mVirtualDisplays.contains(displayId);
+        }
     }
 
     void onEnteringPipBlocked(int uid) {
@@ -1016,10 +1097,6 @@
         }
     }
 
-    interface OnDeviceCloseListener {
-        void onClose(int deviceId);
-    }
-
     interface PendingTrampolineCallback {
         /**
          * Called when the callback should start waiting for the given pending trampoline.
@@ -1073,4 +1150,31 @@
                     + ", displayId=" + mDisplayId + "}";
         }
     }
+
+    /** Data class wrapping resources tied to single virtual display. */
+    private static final class VirtualDisplayWrapper {
+        private final IVirtualDisplayCallback mToken;
+        private final GenericWindowPolicyController mWindowPolicyController;
+        private final PowerManager.WakeLock mWakeLock;
+
+        VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
+                @NonNull GenericWindowPolicyController windowPolicyController,
+                @NonNull PowerManager.WakeLock wakeLock) {
+            mToken = Objects.requireNonNull(token);
+            mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
+            mWakeLock = Objects.requireNonNull(wakeLock);
+        }
+
+        GenericWindowPolicyController getWindowPolicyController() {
+            return mWindowPolicyController;
+        }
+
+        PowerManager.WakeLock getWakeLock() {
+            return mWakeLock;
+        }
+
+        IVirtualDisplayCallback getToken() {
+            return mToken;
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9bb05a6..3b1983f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -37,7 +37,6 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.Context;
 import android.content.Intent;
-import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.os.Binder;
@@ -85,7 +84,7 @@
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
 
     private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
-            VirtualDeviceManager.DEVICE_ID_DEFAULT + 1);
+            Context.DEVICE_ID_DEFAULT + 1);
 
     /**
      * Mapping from device IDs to virtual devices.
@@ -108,26 +107,26 @@
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
 
-        @Nullable
-        @Override
-        public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
-                ActivityInterceptorInfo info) {
-            if (info.getCallingPackage() == null) {
-                return null;
-            }
-            PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
-            if (pt == null) {
-                return null;
-            }
-            pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
-            ActivityOptions options = info.getCheckedOptions();
-            if (options == null) {
-                options = ActivityOptions.makeBasic();
-            }
-            return new ActivityInterceptResult(
-                    info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
-        }
-    };
+                @Nullable
+                @Override
+                public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+                        ActivityInterceptorInfo info) {
+                    if (info.getCallingPackage() == null) {
+                        return null;
+                    }
+                    PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
+                    if (pt == null) {
+                        return null;
+                    }
+                    pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
+                    ActivityOptions options = info.getCheckedOptions();
+                    if (options == null) {
+                        options = ActivityOptions.makeBasic();
+                    }
+                    return new ActivityInterceptResult(
+                            info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
+                }
+            };
 
     @Override
     public void onStart() {
@@ -146,8 +145,8 @@
                 CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
                 mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
                         getContext().getString(
-                            com.android.internal.R.string.vdm_camera_access_denied,
-                            deviceName),
+                                com.android.internal.R.string.vdm_camera_access_denied,
+                                deviceName),
                         Toast.LENGTH_LONG, Looper.myLooper());
             }
         }
@@ -193,34 +192,46 @@
         }
     }
 
-    @VisibleForTesting
     void removeVirtualDevice(int deviceId) {
         synchronized (mVirtualDeviceManagerLock) {
             mAppsOnVirtualDevices.remove(deviceId);
             mVirtualDevices.remove(deviceId);
         }
+
+        Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+        i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
 
         private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
                 new VirtualDeviceImpl.PendingTrampolineCallback() {
-            @Override
-            public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
-                PendingTrampoline existing = mPendingTrampolines.put(
-                        pendingTrampoline.mPendingIntent.getCreatorPackage(),
-                        pendingTrampoline);
-                if (existing != null) {
-                    existing.mResultReceiver.send(
-                            VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
-                }
-            }
+                    @Override
+                    public void startWaitingForPendingTrampoline(
+                            PendingTrampoline pendingTrampoline) {
+                        PendingTrampoline existing = mPendingTrampolines.put(
+                                pendingTrampoline.mPendingIntent.getCreatorPackage(),
+                                pendingTrampoline);
+                        if (existing != null) {
+                            existing.mResultReceiver.send(
+                                    VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
+                        }
+                    }
 
-            @Override
-            public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
-                mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
-            }
-        };
+                    @Override
+                    public void stopWaitingForPendingTrampoline(
+                            PendingTrampoline pendingTrampoline) {
+                        mPendingTrampolines.remove(
+                                pendingTrampoline.mPendingIntent.getCreatorPackage());
+                    }
+                };
 
         @Override // Binder call
         public IVirtualDevice createVirtualDevice(
@@ -251,8 +262,9 @@
                 final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
                         runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, token, callingUid, deviceId, cameraAccessController,
-                        this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
+                        associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+                        deviceId, cameraAccessController,
+                        mPendingTrampolineCallback, activityListener,
                         soundEffectListener, runningAppsChangedCallback, params);
                 mVirtualDevices.put(deviceId, virtualDevice);
                 return virtualDevice;
@@ -281,26 +293,9 @@
                         "uid " + callingUid
                                 + " is not the owner of the supplied VirtualDevice");
             }
-            GenericWindowPolicyController gwpc;
-            final long token = Binder.clearCallingIdentity();
-            try {
-                gwpc = virtualDeviceImpl.createWindowPolicyController(
-                    virtualDisplayConfig.getDisplayCategories());
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
 
-            DisplayManagerInternal displayManager = getLocalService(
-                    DisplayManagerInternal.class);
-            int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
-                    virtualDevice, gwpc, packageName);
-
-            final long tokenTwo = Binder.clearCallingIdentity();
-            try {
-                virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId);
-            } finally {
-                Binder.restoreCallingIdentity(tokenTwo);
-            }
+            int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback,
+                    packageName);
             mLocalService.onVirtualDisplayCreated(displayId);
             return displayId;
         }
@@ -332,7 +327,7 @@
         @Override // Binder call
         public int getDeviceIdForDisplayId(int displayId) {
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
-                return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+                return Context.DEVICE_ID_DEFAULT;
             }
             synchronized (mVirtualDeviceManagerLock) {
                 for (int i = 0; i < mVirtualDevices.size(); i++) {
@@ -342,7 +337,7 @@
                     }
                 }
             }
-            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+            return Context.DEVICE_ID_DEFAULT;
         }
 
         // Binder call
@@ -412,19 +407,6 @@
             return null;
         }
 
-        private void onDeviceClosed(int deviceId) {
-            removeVirtualDevice(deviceId);
-            Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
-            i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
-            i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                getContext().sendBroadcastAsUser(i, UserHandle.ALL);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -512,9 +494,14 @@
         @Override
         public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
             final VirtualDisplayListener[] listeners;
+            VirtualDeviceImpl virtualDeviceImpl;
             synchronized (mVirtualDeviceManagerLock) {
-                ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
                 listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
+                virtualDeviceImpl = mVirtualDevices.get(
+                        ((VirtualDeviceImpl) virtualDevice).getDeviceId());
+            }
+            if (virtualDeviceImpl != null) {
+                virtualDeviceImpl.onVirtualDisplayRemoved(displayId);
             }
             mHandler.post(() -> {
                 for (VirtualDisplayListener listener : listeners) {
@@ -599,16 +586,11 @@
 
         @Override
         public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) {
+            VirtualDeviceImpl virtualDevice;
             synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.getDeviceId() == deviceId) {
-                        return new ArraySet<>(device.mVirtualDisplayIds);
-                    }
-                }
+                virtualDevice = mVirtualDevices.get(deviceId);
             }
-            return new ArraySet<>();
+            return virtualDevice == null ? new ArraySet<>() : virtualDevice.getDisplayIds();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 74f7990..4a134ee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16353,6 +16353,7 @@
 
     // TODO(b/111541062): This method is only used for updating OOM adjustments. We need to update
     // the logic there and in mBatteryStatsService to make them aware of multiple resumed activities
+    @Nullable
     ProcessRecord getTopApp() {
         final WindowProcessController wpc = mAtmInternal != null ? mAtmInternal.getTopApp() : null;
         final ProcessRecord r = wpc != null ? (ProcessRecord) wpc.mOwner : null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index aa9d4cc..72d6ca9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3531,9 +3531,14 @@
             if (foregroundActivities) {
                 try {
                     int prcState = mIam.getUidProcessState(uid, "android");
-                    int topPid = mInternal.getTopApp().getPid();
-                    if (prcState == ProcessStateEnum.TOP && topPid == pid) {
-                        mPw.println("New foreground process: " + pid);
+                    ProcessRecord topApp = mInternal.getTopApp();
+                    if (topApp == null) {
+                        mPw.println("No top app found");
+                    } else {
+                        int topPid = topApp.getPid();
+                        if (prcState == ProcessStateEnum.TOP && topPid == pid) {
+                            mPw.println("New foreground process: " + pid);
+                        }
                     }
                     mPw.flush();
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 463a2f8..16219cd 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -49,7 +49,7 @@
      * this time, the information might be outdated. So we only the dump the unresponsive process
      * instead of including other processes to avoid making the system more busy.
      */
-    private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.MINUTES.toMillis(1);
+    private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
 
     /**
      * If the last ANR occurred within this given time, consider it's anomaly.
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index dce88da..005ad20 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -23,6 +23,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.hardware.biometrics.AuthenticateOptions;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
@@ -44,8 +45,8 @@
 /**
  * A class to keep track of the authentication state for a given client.
  */
-public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
-        implements AuthenticationConsumer {
+public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
+        extends AcquisitionClient<T> implements AuthenticationConsumer {
 
     // New, has not started yet
     public static final int STATE_NEW = 0;
@@ -89,14 +90,15 @@
 
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int targetUserId, long operationId, boolean restricted, @NonNull String owner,
-            int cookie, boolean requireConfirmation, int sensorId,
+            long operationId, boolean restricted, @NonNull O options,
+            int cookie, boolean requireConfirmation,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             boolean shouldVibrate, int sensorStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
-                shouldVibrate, biometricLogger, biometricContext);
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), cookie, options.getSensorId(), shouldVibrate,
+                biometricLogger, biometricContext);
         mIsStrongBiometric = isStrongBiometric;
         mOperationId = operationId;
         mRequireConfirmation = requireConfirmation;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
index 0f1fe68..25651fa28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.util.proto.ProtoOutputStream;
 
@@ -39,7 +40,7 @@
     List<T> getSensorProperties();
 
     /** Properties for the given sensor id. */
-    @NonNull
+    @Nullable
     T getSensorProperties(int sensorId);
 
     boolean isHardwareDetected(int sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 2263e80..a4b0a0e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -92,7 +92,7 @@
         final int previousBiometricState = mBiometricState;
 
         if (client instanceof AuthenticationClient) {
-            final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
+            final AuthenticationClient<?, ?> authClient = (AuthenticationClient<?, ?>) client;
             if (authClient.isKeyguard()) {
                 mBiometricState = STATE_KEYGUARD_AUTH;
             } else if (authClient.isBiometricPrompt()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index cdf22aa..69ad152 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -64,7 +64,6 @@
     private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>();
     private final BiometricUtils<S> mBiometricUtils;
     private final Map<Integer, Long> mAuthenticatorIds;
-    private final List<S> mEnrolledList;
     private final boolean mHasEnrollmentsBeforeStarting;
     private BaseClientMonitor mCurrentTask;
     private boolean mFavorHalEnrollments = false;
@@ -135,13 +134,12 @@
     protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
+            @NonNull BiometricUtils<S> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
                 userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
-        mEnrolledList = enrolledList;
         mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
     }
 
@@ -169,12 +167,16 @@
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
+        final List<S> enrolledList =
+                mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId());
+
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
-                getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(),
+                getOwnerString(), enrolledList, mBiometricUtils, getSensorId(), getLogger(),
                 getBiometricContext());
 
-        Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
+        Slog.d(TAG, "Starting enumerate: " + mCurrentTask + " enrolledList size:"
+                + enrolledList.size());
         mCurrentTask.start(mEnumerateCallback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 5182968..fb64bcc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -64,9 +64,10 @@
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
-        mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
+        mFaceService.prepareForAuthentication(requireConfirmation, token, operationId,
                 sensorReceiver, new FaceAuthenticateOptions.Builder()
                         .setUserId(userId)
+                        .setSensorId(mSensorId)
                         .setOpPackageName(opPackageName)
                         .build(),
                 requestId, cookie, allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 1ee9f53..6d7b2cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -246,7 +246,6 @@
 
             super.authenticate_enforcePermission();
 
-            final int userId = options.getUserId();
             final String opPackageName = options.getOpPackageName();
             final boolean restricted = false; // Face APIs are private
             final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
@@ -261,9 +260,11 @@
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
                 return -1;
+            } else {
+                options.setSensorId(provider.first);
             }
 
-            return provider.second.scheduleAuthenticate(provider.first, token, operationId,
+            return provider.second.scheduleAuthenticate(token, operationId,
                     0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
                     restricted, statsClient, isKeyguard);
         }
@@ -286,28 +287,27 @@
                 return -1;
             }
 
-            return provider.second.scheduleFaceDetect(provider.first, token, options.getUserId(),
-                    new ClientMonitorCallbackConverter(receiver), opPackageName,
+            return provider.second.scheduleFaceDetect(token,
+                    new ClientMonitorCallbackConverter(receiver), options,
                     BiometricsProtoEnums.CLIENT_KEYGUARD);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
-        public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
+        public void prepareForAuthentication(boolean requireConfirmation,
                 IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver,
                 FaceAuthenticateOptions options, long requestId, int cookie,
                 boolean allowBackgroundAuthentication) {
             super.prepareForAuthentication_enforcePermission();
 
-            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId());
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
             }
 
-            final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
             final boolean restricted = true; // BiometricPrompt is always restricted
-            provider.scheduleAuthenticate(sensorId, token, operationId, cookie,
+            provider.scheduleAuthenticate(token, operationId, cookie,
                     new ClientMonitorCallbackConverter(sensorReceiver), options, requestId,
                     restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                     allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 609c6a7..2cf64b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -83,18 +83,19 @@
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
 
-    long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+    long scheduleFaceDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FaceAuthenticateOptions options,
             int statsClient);
 
     void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
 
-    long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options,
             boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
 
-    void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options, long requestId,
             boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 29dd707..976f1cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.IFace;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -56,7 +57,7 @@
 /**
  * Face-specific authentication client for the {@link IFace} AIDL HAL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession>
+class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
         implements LockoutConsumer {
     private static final String TAG = "FaceAuthenticationClient";
 
@@ -80,16 +81,16 @@
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+            boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
             @Authenticators.Types int sensorStrength) {
-        this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
-                restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, usageStats, lockoutCache /* lockoutCache */,
-                allowBackgroundAuthentication,
+        this(context, lazyDaemon, token, requestId, listener, operationId,
+                restricted, options, cookie, requireConfirmation, logger, biometricContext,
+                isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
                 context.getSystemService(SensorPrivacyManager.class), sensorStrength);
     }
 
@@ -97,15 +98,16 @@
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+            boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
             @Authenticators.Types int biometricStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+        super(context, lazyDaemon, token, listener, operationId, restricted,
+                options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
                 allowBackgroundAuthentication, false /* shouldVibrate */,
                 biometricStrength);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 506b2bc..e65202d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -51,11 +52,11 @@
 
     FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FaceAuthenticateOptions options,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric) {
-        this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+        this(context, lazyDaemon, token, requestId, listener, options,
                 logger, biometricContext, isStrongBiometric,
                 context.getSystemService(SensorPrivacyManager.class));
     }
@@ -63,11 +64,12 @@
     @VisibleForTesting
     FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FaceAuthenticateOptions options,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index b0b23fa..f09d192 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -43,10 +43,10 @@
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricContext biometricContext,
             @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 41e0269..1a53fec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -410,16 +410,17 @@
     }
 
     @Override
-    public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
-            int userId, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, int statsClient) {
+    public long scheduleFaceDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FaceAuthenticateOptions options, int statsClient) {
         final long id = mRequestCounter.incrementAndGet();
+        final int sensorId = options.getSensorId();
 
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mSensors.get(sensorId).getLazySession(),
-                    token, id, callback, userId, opPackageName, sensorId,
+                    token, id, callback, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -435,18 +436,19 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options,
             long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
             final int userId = options.getUserId();
+            final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
-                    userId, operationId, restricted, options.getOpPackageName(), cookie,
-                    false /* requireConfirmation */, sensorId,
+                    operationId, restricted, options, cookie,
+                    false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
@@ -470,13 +472,13 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, cookie, callback,
+        scheduleAuthenticate(token, operationId, cookie, callback,
                 options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
@@ -595,14 +597,13 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
         mHandler.post(() -> {
-            final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
-                            mBiometricContext, enrolledList,
+                            mBiometricContext,
                             FaceUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 7e575bc..1e33c96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -651,9 +651,9 @@
     }
 
     @Override
-    public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
-            int userId, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, int statsClient) {
+    public long scheduleFaceDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FaceAuthenticateOptions options, int statsClient) {
         throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
                 + "forget to check the supportsFaceDetection flag?");
     }
@@ -665,7 +665,7 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter receiver,
             @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
             int statsClient, boolean allowBackgroundAuthentication) {
@@ -675,8 +675,8 @@
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
-                    mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
-                    options.getOpPackageName(), cookie, false /* requireConfirmation */, mSensorId,
+                    mLazyDaemon, token, requestId, receiver, operationId, restricted,
+                    options, cookie, false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
                     mUsageStats, allowBackgroundAuthentication,
@@ -686,13 +686,13 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter receiver,
             @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, cookie, receiver,
+        scheduleAuthenticate(token, operationId, cookie, receiver,
                 options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
@@ -818,12 +818,11 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId);
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext, enrolledList,
+                    mBiometricContext,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
                     mBiometricStateCallback));
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 1c1f56c..8ab8892 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -25,6 +25,7 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -50,7 +51,8 @@
  * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
  * HIDL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
+class FaceAuthenticationClient
+        extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> {
 
     private static final String TAG = "FaceAuthenticationClient";
 
@@ -67,17 +69,18 @@
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+            boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
             @Authenticators.Types int sensorStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+        super(context, lazyDaemon, token, listener, operationId, restricted,
+                options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
-                 sensorStrength);
+                sensorStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index d21a750..89a17c6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -42,10 +42,10 @@
     FaceInternalCleanupClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
             int sensorId, @NonNull BiometricLogger logger,
-            @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+            @NonNull BiometricContext biometricContext,
             @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 52d887a..d47a57a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IFingerprintService;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -63,8 +64,13 @@
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
-        mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
-                sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
+        mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setSensorId(mSensorId)
+                        .setUserId(userId)
+                        .setOpPackageName(opPackageName)
+                        .build(),
+                requestId, cookie, allowBackgroundAuthentication);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index affc496e..8a33f22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -119,7 +119,7 @@
     @NonNull
     private final Supplier<String[]> mAidlInstanceNameSupplier;
     @NonNull
-    private final Function<String, IFingerprint> mIFingerprintProvider;
+    private final Function<String, FingerprintProvider> mFingerprintProvider;
     @NonNull
     private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
             mBiometricStateCallback;
@@ -307,6 +307,8 @@
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
                 return -1;
+            } else {
+                options.setSensorId(provider.first);
             }
 
             final FingerprintSensorPropertiesInternal sensorProps =
@@ -322,8 +324,8 @@
                     return -1;
                 }
             }
-            return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
-                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+            return provider.second.scheduleAuthenticate(token, operationId,
+                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
                     restricted, statsClient, isKeyguard);
         }
 
@@ -436,29 +438,32 @@
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
                 return -1;
+            } else {
+                options.setSensorId(provider.first);
             }
 
-            return provider.second.scheduleFingerDetect(provider.first, token, options.getUserId(),
-                    new ClientMonitorCallbackConverter(receiver), opPackageName,
+            return provider.second.scheduleFingerDetect(token,
+                    new ClientMonitorCallbackConverter(receiver), options,
                     BiometricsProtoEnums.CLIENT_KEYGUARD);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
-        public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
-                int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
+        public void prepareForAuthentication(IBinder token, long operationId,
+                IBiometricSensorReceiver sensorReceiver,
+                @NonNull FingerprintAuthenticateOptions options,
                 long requestId, int cookie, boolean allowBackgroundAuthentication) {
             super.prepareForAuthentication_enforcePermission();
 
-            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId());
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
             }
 
             final boolean restricted = true; // BiometricPrompt is always restricted
-            provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
-                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+            provider.scheduleAuthenticate(token, operationId, cookie,
+                    new ClientMonitorCallbackConverter(sensorReceiver), options, requestId,
                     restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                     allowBackgroundAuthentication);
         }
@@ -982,8 +987,7 @@
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
                 () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
-                (fqName) -> IFingerprint.Stub.asInterface(
-                        Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
+                null /* fingerprintProvider */);
     }
 
     @VisibleForTesting
@@ -991,16 +995,35 @@
             BiometricContext biometricContext,
             Supplier<IBiometricService> biometricServiceSupplier,
             Supplier<String[]> aidlInstanceNameSupplier,
-            Function<String, IFingerprint> fingerprintProvider) {
+            Function<String, FingerprintProvider> fingerprintProvider) {
         super(context);
         mBiometricContext = biometricContext;
         mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
-        mIFingerprintProvider = fingerprintProvider;
         mAppOps = context.getSystemService(AppOpsManager.class);
         mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider :
+                (name) -> {
+                    final String fqName = IFingerprint.DESCRIPTOR + "/" + name;
+                    final IFingerprint fp = IFingerprint.Stub.asInterface(
+                            Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+                    if (fp != null) {
+                        try {
+                            return new FingerprintProvider(getContext(),
+                                    mBiometricStateCallback, fp.getSensorProps(), name,
+                                    mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+                                    mBiometricContext);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+                        }
+                    } else {
+                        Slog.e(TAG, "Unable to get declared service: " + fqName);
+                    }
+
+                    return null;
+                };
         mHandler = new Handler(Looper.getMainLooper());
         mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1044,23 +1067,9 @@
         final List<ServiceProvider> providers = new ArrayList<>();
 
         for (String instance : instances) {
-            final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
-            final IFingerprint fp = mIFingerprintProvider.apply(fqName);
-
-            if (fp != null) {
-                try {
-                    final FingerprintProvider provider = new FingerprintProvider(getContext(),
-                            mBiometricStateCallback, fp.getSensorProps(), instance,
-                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
-                            mBiometricContext);
-                    Slog.i(TAG, "Adding AIDL provider: " + fqName);
-                    providers.add(provider);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
-                }
-            } else {
-                Slog.e(TAG, "Unable to get declared service: " + fqName);
-            }
+            final FingerprintProvider provider = mFingerprintProvider.apply(instance);
+            Slog.i(TAG, "Adding AIDL provider: " + instance);
+            providers.add(provider);
         }
 
         return providers;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 5b6f14d..004af2c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -78,19 +79,21 @@
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
 
-    long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+    long scheduleFingerDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options,
             int statsClient);
 
-    void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+    void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication);
 
-    long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+    long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication);
+            @NonNull FingerprintAuthenticateOptions options,
+            boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
 
     void startPreparedClient(int sensorId, int cookie);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index d1a7b13..0f81f9f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlay;
@@ -65,7 +66,8 @@
  * Fingerprint-specific authentication client supporting the {@link
  * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
+class FingerprintAuthenticationClient
+        extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
     private static final int MESSAGE_AUTH_SUCCESS = 2;
@@ -97,13 +99,11 @@
             @NonNull IBinder token,
             long requestId,
             @NonNull ClientMonitorCallbackConverter listener,
-            int targetUserId,
             long operationId,
             boolean restricted,
-            @NonNull String owner,
+            @NonNull FingerprintAuthenticateOptions options,
             int cookie,
             boolean requireConfirmation,
-            int sensorId,
             @NonNull BiometricLogger biometricLogger,
             @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric,
@@ -122,13 +122,11 @@
                 lazyDaemon,
                 token,
                 listener,
-                targetUserId,
                 operationId,
                 restricted,
-                owner,
+                options,
                 cookie,
                 requireConfirmation,
-                sensorId,
                 biometricLogger,
                 biometricContext,
                 isStrongBiometric,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index f6911ea..376d231 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -52,13 +53,14 @@
 
     FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable IUdfpsOverlay udfpsOverlay,
             boolean isStrongBiometric) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index c315ccf..ff9127f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -45,10 +45,9 @@
             @NonNull Supplier<AidlSession> lazyDaemon,
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull List<Fingerprint> enrolledList,
             @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 776d331..23b6f84 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -37,6 +37,7 @@
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -422,15 +423,17 @@
     }
 
     @Override
-    public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+    public long scheduleFingerDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options,
             int statsClient) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
+            final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
-                    opPackageName, sensorId,
+                    mSensors.get(sensorId).getLazySession(), token, id, callback,
+                    options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext,
                     mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
@@ -441,16 +444,19 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
+            final int userId = options.getUserId();
+            final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
-                    userId, operationId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */, sensorId,
+                    operationId, restricted, options, cookie,
+                    false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
@@ -485,14 +491,14 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
-                opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+        scheduleAuthenticate(token, operationId, cookie, callback,
+                options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
     }
@@ -556,7 +562,6 @@
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
         mHandler.post(() -> {
-            final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
             final FingerprintInternalCleanupClient client =
                     new FingerprintInternalCleanupClient(mContext,
                             mSensors.get(sensorId).getLazySession(), userId,
@@ -564,7 +569,7 @@
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext,
-                            enrolledList, FingerprintUtils.getInstance(sensorId),
+                            FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
                 client.setFavorHalEnrollments();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 4567addc..9e6f4e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -631,17 +632,17 @@
     }
 
     @Override
-    public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
+    public long scheduleFingerDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
             int statsClient) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
+            scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mLazyDaemon, token, id, listener, userId, opPackageName,
-                    mSensorProperties.sensorId,
+                    mLazyDaemon, token, id, listener, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
                     isStrongBiometric);
@@ -652,18 +653,18 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
+            scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
-                    restricted, opPackageName, cookie, false /* requireConfirmation */,
-                    mSensorProperties.sensorId,
+                    mContext, mLazyDaemon, token, requestId, listener, operationId,
+                    restricted, options, cookie, false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
@@ -675,14 +676,14 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
-                opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+        scheduleAuthenticate(token, operationId, cookie, listener,
+                options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
     }
@@ -741,14 +742,12 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            final List<Fingerprint> enrolledList = getEnrolledFingerprints(
-                    mSensorProperties.sensorId, userId);
             final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext, enrolledList,
+                    mBiometricContext,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 73b1288..0a47c12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -362,13 +363,16 @@
             // Store the authClient parameters so it can be rescheduled
             final IBinder token = client.getToken();
             final long operationId = authClient.getOperationId();
-            final int user = client.getTargetUserId();
             final int cookie = client.getCookie();
             final ClientMonitorCallbackConverter listener = client.getListener();
-            final String opPackageName = client.getOwnerString();
             final boolean restricted = authClient.isRestricted();
             final int statsClient = client.getLogger().getStatsClient();
             final boolean isKeyguard = authClient.isKeyguard();
+            final FingerprintAuthenticateOptions options =
+                    new FingerprintAuthenticateOptions.Builder()
+                            .setUserId(client.getTargetUserId())
+                            .setOpPackageName(client.getOwnerString())
+                            .build();
 
             // Don't actually send cancel() to the HAL, since successful auth already finishes
             // HAL authenticate() lifecycle. Just
@@ -376,8 +380,8 @@
 
             // Schedule this only after we invoke onClientFinished for the previous client, so that
             // internal preemption logic is not run.
-            mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
-                    operationId, user, cookie, listener, opPackageName, restricted, statsClient,
+            mFingerprint21.scheduleAuthenticate(token,
+                    operationId, cookie, listener, options, restricted, statsClient,
                     isKeyguard);
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 957005a..d22aef8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlay;
@@ -57,7 +58,8 @@
  * {@link android.hardware.biometrics.fingerprint.V2_1} and
  * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
  */
-class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFingerprint>
+class FingerprintAuthenticationClient
+        extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions>
         implements Udfps {
 
     private static final String TAG = "Biometrics/FingerprintAuthClient";
@@ -72,9 +74,9 @@
     FingerprintAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FingerprintAuthenticateOptions options,
+            int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
@@ -84,8 +86,8 @@
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Authenticators.Types int sensorStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+        super(context, lazyDaemon, token, listener, operationId, restricted,
+                options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
                 false /* shouldVibrate */, sensorStrength);
         setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index cfa9fb4..362c820 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -24,6 +24,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -61,12 +62,13 @@
     public FingerprintDetectClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 5e7cf35..8b61f59 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -45,11 +45,10 @@
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull List<Fingerprint> enrolledList,
             @NonNull BiometricUtils<Fingerprint> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
-                enrolledList, utils, authenticatorIds);
+                utils, authenticatorIds);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index dd92ffc..fab138b 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -18,9 +18,9 @@
 
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
 
 import android.Manifest;
 import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ab2c002..1ce917c 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3254,6 +3254,8 @@
             }
 
             mActiveNetwork = network;
+            mUnderlyingLinkProperties = null;
+            mUnderlyingNetworkCapabilities = null;
             mRetryCount = 0;
 
             startOrMigrateIkeSession(network);
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 8e34601..fcaa957 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -4,5 +4,7 @@
 ogunwale@google.com
 santoscordon@google.com
 flc@google.com
+wilczynskip@google.com
+brup@google.com
 
 per-file ColorDisplayService.java=christyfranks@google.com
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index da65f27..2efb0be 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -24,13 +24,14 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.util.Log;
 import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.view.IImeTracker;
 
 import java.io.PrintWriter;
 import java.time.Instant;
@@ -53,7 +54,7 @@
 @SuppressWarnings("GuardedBy")
 public final class ImeTrackerService extends IImeTracker.Stub {
 
-    static final String TAG = "ImeTrackerService";
+    private static final String TAG = ImeTracker.TAG;
 
     /** The threshold in milliseconds after which a history entry is considered timed out. */
     private static final long TIMEOUT_MS = 10_000;
@@ -71,67 +72,71 @@
 
     @NonNull
     @Override
-    public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        final IBinder binder = new Binder();
-        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
-                ImeTracker.STATUS_RUN, origin, reason);
+    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final var binder = new Binder();
+        final var token = new ImeTracker.Token(binder, tag);
+        final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
+                origin, reason);
         mHistory.addEntry(binder, entry);
 
         // Register a delayed task to handle the case where the new entry times out.
         mHandler.postDelayed(() -> {
             synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
             }
         }, TIMEOUT_MS);
 
-        return binder;
+        return token;
     }
 
     @NonNull
     @Override
-    public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        final IBinder binder = new Binder();
-        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
-                ImeTracker.STATUS_RUN, origin, reason);
+    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final var binder = new Binder();
+        final var token = new ImeTracker.Token(binder, tag);
+        final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
+                origin, reason);
         mHistory.addEntry(binder, entry);
 
         // Register a delayed task to handle the case where the new entry times out.
         mHandler.postDelayed(() -> {
             synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
             }
         }, TIMEOUT_MS);
 
-        return binder;
+        return token;
     }
 
     @Override
-    public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
-        final History.Entry entry = mHistory.getEntry(statsToken);
+    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+        final var entry = mHistory.getEntry(binder);
         if (entry == null) return;
 
         entry.mPhase = phase;
     }
 
     @Override
-    public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
+            @ImeTracker.Phase int phase) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
     }
 
     @Override
-    public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
+            @ImeTracker.Phase int phase) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
     }
 
     @Override
-    public synchronized void onShown(@NonNull IBinder statsToken) {
+    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
     }
 
     @Override
-    public synchronized void onHidden(@NonNull IBinder statsToken) {
+    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
     }
 
@@ -141,9 +146,9 @@
      * @param statsToken the token corresponding to the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
-    public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
             @NonNull String requestWindowName) {
-        final History.Entry entry = mHistory.getEntry(statsToken);
+        final var entry = mHistory.getEntry(statsToken.getBinder());
         if (entry == null) return;
 
         entry.mRequestWindowName = requestWindowName;
@@ -181,17 +186,17 @@
         /** Latest entry sequence number. */
         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
 
-        /** Adds a live entry. */
+        /** Adds a live entry corresponding to the given IME tracking token's binder. */
         @GuardedBy("ImeTrackerService.this")
-        private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
-            mLiveEntries.put(statsToken, entry);
+        private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) {
+            mLiveEntries.put(binder, entry);
         }
 
-        /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+        /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */
         @Nullable
         @GuardedBy("ImeTrackerService.this")
-        private Entry getEntry(@NonNull IBinder statsToken) {
-            return mLiveEntries.get(statsToken);
+        private Entry getEntry(@NonNull IBinder binder) {
+            return mLiveEntries.get(binder);
         }
 
         /**
@@ -204,10 +209,21 @@
          *              (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
          */
         @GuardedBy("ImeTrackerService.this")
-        private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
-                @ImeTracker.Phase int phase) {
-            final Entry entry = mLiveEntries.remove(statsToken);
-            if (entry == null) return;
+        private void setFinished(@NonNull ImeTracker.Token statsToken,
+                @ImeTracker.Status int status, @ImeTracker.Phase int phase) {
+            final var entry = mLiveEntries.remove(statsToken.getBinder());
+            if (entry == null) {
+                // This will be unconditionally called through the postDelayed above to handle
+                // potential timeouts, and is thus intentionally dropped to avoid having to manually
+                // save and remove the registered callback. Only timeout calls are expected.
+                if (status != ImeTracker.STATUS_TIMEOUT) {
+                    Log.i(TAG, statsToken.getTag()
+                            + ": setFinished on previously finished token at "
+                            + ImeTracker.Debug.phaseToString(phase) + " with "
+                            + ImeTracker.Debug.statusToString(status));
+                }
+                return;
+            }
 
             entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
             entry.mStatus = status;
@@ -216,6 +232,13 @@
                 entry.mPhase = phase;
             }
 
+            if (status == ImeTracker.STATUS_TIMEOUT) {
+                // All events other than timeouts are already logged in the client-side ImeTracker.
+                Log.i(TAG, statsToken.getTag() + ": setFinished at "
+                        + ImeTracker.Debug.phaseToString(entry.mPhase) + " with "
+                        + ImeTracker.Debug.statusToString(status));
+            }
+
             // Remove excess entries overflowing capacity (plus one for the new entry).
             while (mEntries.size() >= CAPACITY) {
                 mEntries.remove();
@@ -232,21 +255,22 @@
         /** Dumps the contents of the circular buffer. */
         @GuardedBy("ImeTrackerService.this")
         private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
+            final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                    .withZone(ZoneId.systemDefault());
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mLiveEntries:");
+            pw.println("ImeTrackerService#History.mLiveEntries: "
+                    + mLiveEntries.size() + " elements");
 
-            for (final Entry entry: mLiveEntries.values()) {
+            for (final var entry: mLiveEntries.values()) {
                 dumpEntry(entry, pw, prefix, formatter);
             }
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mEntries:");
+            pw.println("ImeTrackerService#History.mEntries: "
+                    + mEntries.size() + " elements");
 
-            for (final Entry entry: mEntries) {
+            for (final var entry: mEntries) {
                 dumpEntry(entry, pw, prefix, formatter);
             }
         }
@@ -255,34 +279,22 @@
         private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
                 @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
             pw.print(prefix);
-            pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+            pw.print(" #" + entry.mSequenceNumber);
+            pw.print(" " + ImeTracker.Debug.typeToString(entry.mType));
+            pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus));
+            pw.print(" - " + entry.mTag);
+            pw.println(" (" + entry.mDuration + "ms):");
 
             pw.print(prefix);
-            pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.print("   startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin));
 
             pw.print(prefix);
-            pw.println(" duration=" + entry.mDuration + "ms");
+            pw.print("   reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+            pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase));
 
             pw.print(prefix);
-            pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
-
-            pw.print(prefix);
-            pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
-
-            pw.print(prefix);
-            pw.print(" origin="
-                    + ImeTracker.Debug.originToString(entry.mOrigin));
-
-            pw.print(prefix);
-            pw.print(" reason="
-                    + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
-
-            pw.print(prefix);
-            pw.print(" phase="
-                    + ImeTracker.Debug.phaseToString(entry.mPhase));
-
-            pw.print(prefix);
-            pw.print(" requestWindowName=" + entry.mRequestWindowName);
+            pw.println("   requestWindowName=" + entry.mRequestWindowName);
         }
 
         /** A history entry. */
@@ -291,6 +303,10 @@
             /** The entry's sequence number in the history. */
             private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
 
+            /** Logging tag, of the shape "component:random_hexadecimal". */
+            @NonNull
+            private final String mTag;
+
             /** Uid of the client that requested the IME. */
             private final int mUid;
 
@@ -323,13 +339,15 @@
             /**
              * Name of the window that created the IME request.
              *
-             * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+             * Note: This is later set through {@link #onImmsUpdate}.
              */
             @NonNull
             private String mRequestWindowName = "not set";
 
-            private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
-                    @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+            private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type,
+                    @ImeTracker.Status int status, @ImeTracker.Origin int origin,
+                    @SoftInputShowHideReason int reason) {
+                mTag = tag;
                 mUid = uid;
                 mType = type;
                 mStatus = status;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 0ea64ab..8c7658e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -178,6 +178,14 @@
     public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
 
     /**
+     * Switch the keyboard layout in response to a keyboard shortcut.
+     *
+     * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
+     *                           previous subtype.
+     */
+    public abstract void switchKeyboardLayout(int direction);
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
@@ -256,6 +264,10 @@
                 @Override
                 public void maybeFinishStylusHandwriting() {
                 }
+
+                @Override
+                public void switchKeyboardLayout(int direction) {
+                }
             };
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8ef4e4a..eba261a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -145,6 +145,7 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
 import com.android.internal.inputmethod.IInputMethod;
@@ -168,7 +169,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
@@ -532,6 +532,7 @@
     /**
      * The client that is currently bound to an input method.
      */
+    @Nullable
     private ClientState mCurClient;
 
     /**
@@ -557,11 +558,26 @@
     int mCurFocusedWindowSoftInputMode;
 
     /**
-     * The client by which {@link #mCurFocusedWindow} was reported.
+     * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
+     * IME-focusable window gained focus (without necessarily starting an input connection),
+     * while {@link #mCurClient} only gets updated when we actually start an input connection.
+     *
+     * @see #mCurFocusedWindow
      */
+    @Nullable
     ClientState mCurFocusedWindowClient;
 
     /**
+     * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
+     * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
+     * from {@link #mCurClient}.
+     *
+     * @see #mCurFocusedWindow
+     */
+    @Nullable
+    EditorInfo mCurFocusedWindowEditorInfo;
+
+    /**
      * The {@link IRemoteInputConnection} last provided by the current client.
      */
     IRemoteInputConnection mCurInputConnection;
@@ -580,6 +596,7 @@
     /**
      * The {@link EditorInfo} last provided by the current client.
      */
+    @Nullable
     EditorInfo mCurEditorInfo;
 
     /**
@@ -2062,7 +2079,7 @@
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, directBootAwareness);
+                    methodList, directBootAwareness, mSettings.getEnabledInputMethodNames());
             settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
         }
         // filter caller's access to input methods
@@ -2265,6 +2282,7 @@
                 }
                 if (mCurFocusedWindowClient == cs) {
                     mCurFocusedWindowClient = null;
+                    mCurFocusedWindowEditorInfo = null;
                 }
             }
         }
@@ -3453,10 +3471,7 @@
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            // TODO(b/261565259): to avoid using null, add package name in ClientState
-            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
-            final int uid = mCurClient != null ? mCurClient.mUid : -1;
-            statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+            statsToken = createStatsTokenForFocusedClient(true /* show */,
                     ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
@@ -3530,17 +3545,7 @@
             int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            // TODO(b/261565259): to avoid using null, add package name in ClientState
-            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
-            final int uid;
-            if (mCurClient != null) {
-                uid = mCurClient.mUid;
-            } else if (mCurFocusedWindowClient != null) {
-                uid = mCurFocusedWindowClient.mUid;
-            } else {
-                uid = -1;
-            }
-            statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+            statsToken = createStatsTokenForFocusedClient(false /* show */,
                     ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
@@ -3775,6 +3780,7 @@
         mCurFocusedWindow = windowToken;
         mCurFocusedWindowSoftInputMode = softInputMode;
         mCurFocusedWindowClient = cs;
+        mCurFocusedWindowEditorInfo = editorInfo;
         mCurPerceptible = true;
 
         // We want to start input before showing the IME, but after closing
@@ -4044,17 +4050,22 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
-            if (nextSubtype == null) {
-                return false;
-            }
-            setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
-                    nextSubtype.mSubtypeId);
-            return true;
+            return switchToNextInputMethodLocked(token, onlyCurrentIme);
         }
     }
 
+    @GuardedBy("ImfLock.class")
+    private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+        final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
+                onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
+        if (nextSubtype == null) {
+            return false;
+        }
+        setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
+                nextSubtype.mSubtypeId);
+        return true;
+    }
+
     @BinderThread
     private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) {
         synchronized (ImfLock.class) {
@@ -4130,7 +4141,7 @@
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, DirectBootAwareness.AUTO);
+                    methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames());
             final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
                     userId, false);
             settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
@@ -4703,13 +4714,13 @@
                 mWindowManagerInternal.onToggleImeRequested(
                         show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
-                mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
+                mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
                 info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
                 info.imeSurfaceParentName));
 
         if (statsToken != null) {
-            mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+            mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
         }
     }
 
@@ -5027,7 +5038,7 @@
     static void queryInputMethodServicesInternal(Context context,
             @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
             ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
-            @DirectBootAwareness int directBootAwareness) {
+            @DirectBootAwareness int directBootAwareness, List<String> enabledInputMethodList) {
         final Context userAwareContext = context.getUserId() == userId
                 ? context
                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
@@ -5060,6 +5071,17 @@
         methodList.ensureCapacity(services.size());
         methodMap.ensureCapacity(services.size());
 
+        filterInputMethodServices(additionalSubtypeMap, methodMap, methodList,
+                enabledInputMethodList, userAwareContext, services);
+    }
+
+    static void filterInputMethodServices(
+            ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+            List<String> enabledInputMethodList, Context userAwareContext,
+            List<ResolveInfo> services) {
+        final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
+
         for (int i = 0; i < services.size(); ++i) {
             ResolveInfo ri = services.get(i);
             ServiceInfo si = ri.serviceInfo;
@@ -5079,10 +5101,21 @@
                 if (imi.isVrOnly()) {
                     continue;  // Skip VR-only IME, which isn't supported for now.
                 }
-                methodList.add(imi);
-                methodMap.put(imi.getId(), imi);
-                if (DEBUG) {
-                    Slog.d(TAG, "Found an input method " + imi);
+                final String packageName = si.packageName;
+                // only include IMEs which are from the system, enabled, or below the threshold
+                if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId())
+                        || imiPackageCount.getOrDefault(packageName, 0)
+                        < InputMethodInfo.MAX_IMES_PER_PACKAGE) {
+                    imiPackageCount.put(packageName,
+                            1 + imiPackageCount.getOrDefault(packageName, 0));
+
+                    methodList.add(imi);
+                    methodMap.put(imi.getId(), imi);
+                    if (DEBUG) {
+                        Slog.d(TAG, "Found an input method " + imi);
+                    }
+                } else if (DEBUG) {
+                    Slog.d(TAG, "Found an input method, but ignored due threshold: " + imi);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
@@ -5104,7 +5137,8 @@
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
         queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
-                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO);
+                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO,
+                mSettings.getEnabledInputMethodNames());
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -5163,7 +5197,7 @@
                             reenableMinimumNonAuxSystemImes);
             final int numImes = defaultEnabledIme.size();
             for (int i = 0; i < numImes; ++i) {
-                final InputMethodInfo imi =  defaultEnabledIme.get(i);
+                final InputMethodInfo imi = defaultEnabledIme.get(i);
                 if (DEBUG) {
                     Slog.d(TAG, "--- enable ime = " + imi);
                 }
@@ -5463,7 +5497,8 @@
                 new ArrayMap<>();
         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                methodMap, methodList, DirectBootAwareness.AUTO);
+                methodMap, methodList, DirectBootAwareness.AUTO,
+                mSettings.getEnabledInputMethodNames());
         return methodMap;
     }
 
@@ -5719,6 +5754,17 @@
             mHandler.removeMessages(MSG_FINISH_HANDWRITING);
             mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget();
         }
+
+        @Override
+        public void switchKeyboardLayout(int direction) {
+            synchronized (ImfLock.class) {
+                if (direction > 0) {
+                    switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */);
+                } else {
+                    // TODO(b/258853866): Support backwards switching.
+                }
+            }
+        }
     }
 
     @BinderThread
@@ -5751,9 +5797,11 @@
             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
             // nature of our system.  Let's compare it with our internal record.
-            if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) {
+            final var curPackageName = mCurEditorInfo != null
+                    ? mCurEditorInfo.packageName : null;
+            if (!TextUtils.equals(curPackageName, packageName)) {
                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
-                        + mCurEditorInfo.packageName + " packageName=" + packageName);
+                        + curPackageName + " packageName=" + packageName);
                 return null;
             }
             // This user ID can never bee spoofed.
@@ -6421,7 +6469,8 @@
                                 new ArrayMap<>();
                         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
                         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                                methodMap, methodList, DirectBootAwareness.AUTO);
+                                methodMap, methodList, DirectBootAwareness.AUTO,
+                                mSettings.getEnabledInputMethodNames());
                         final InputMethodSettings settings = new InputMethodSettings(mContext,
                                 methodMap, userId, false);
 
@@ -6514,6 +6563,30 @@
         return mImeTrackerService;
     }
 
+    /**
+     * Creates an IME request tracking token for the current focused client.
+     *
+     * @param show whether this is a show or a hide request.
+     * @param origin the origin of the IME request.
+     * @param reason the reason why the IME request was created.
+     */
+    @NonNull
+    private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final int uid = mCurFocusedWindowClient != null
+                ? mCurFocusedWindowClient.mUid
+                : -1;
+        final var packageName = mCurFocusedWindowEditorInfo != null
+                ? mCurFocusedWindowEditorInfo.packageName
+                : "uid(" + uid + ")";
+
+        if (show) {
+            return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason);
+        } else {
+            return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason);
+        }
+    }
+
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
         private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 559eb53..17536fc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -438,6 +438,15 @@
                     mSubtypeSplitter);
         }
 
+        List<String> getEnabledInputMethodNames() {
+            List<String> result = new ArrayList<>();
+            for (Pair<String, ArrayList<String>> pair :
+                    getEnabledInputMethodsAndSubtypeListLocked()) {
+                result.add(pair.first);
+            }
+            return result;
+        }
+
         void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
             if (reloadInputMethodStr) {
                 getEnabledInputMethodsStr();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 4013468..0c69855 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -420,7 +420,7 @@
         try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
             setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
                     /* isLockTiedToParent= */ true);
-            tieProfileLockToParent(profileUserId, unifiedProfilePassword);
+            tieProfileLockToParent(profileUserId, parentId, unifiedProfilePassword);
             mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword);
         }
     }
@@ -1887,34 +1887,43 @@
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
-    protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
-        if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
+    protected void tieProfileLockToParent(int profileUserId, int parentUserId,
+            LockscreenCredential password) {
+        if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + profileUserId);
         final byte[] iv;
         final byte[] ciphertext;
+        final long parentSid;
+        try {
+            parentSid = getGateKeeperService().getSecureUserId(parentUserId);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to talk to GateKeeper service", e);
+        }
+
         try {
             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
             keyGenerator.init(new SecureRandom());
             SecretKey secretKey = keyGenerator.generateKey();
             try {
                 mJavaKeyStore.setEntry(
-                        PROFILE_KEY_NAME_ENCRYPT + userId,
+                        PROFILE_KEY_NAME_ENCRYPT + profileUserId,
                         new java.security.KeyStore.SecretKeyEntry(secretKey),
                         new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                                 .build());
                 mJavaKeyStore.setEntry(
-                        PROFILE_KEY_NAME_DECRYPT + userId,
+                        PROFILE_KEY_NAME_DECRYPT + profileUserId,
                         new java.security.KeyStore.SecretKeyEntry(secretKey),
                         new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                                 .setUserAuthenticationRequired(true)
+                                .setBoundToSpecificSecureUserId(parentSid)
                                 .setUserAuthenticationValidityDurationSeconds(30)
                                 .build());
                 // Key imported, obtain a reference to it.
                 SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey(
-                        PROFILE_KEY_NAME_ENCRYPT + userId, null);
+                        PROFILE_KEY_NAME_ENCRYPT + profileUserId, null);
                 Cipher cipher = Cipher.getInstance(
                         KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
                                 + KeyProperties.ENCRYPTION_PADDING_NONE);
@@ -1923,7 +1932,7 @@
                 iv = cipher.getIV();
             } finally {
                 // The original key can now be discarded.
-                mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + userId);
+                mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId);
             }
         } catch (UnrecoverableKeyException
                 | BadPaddingException | IllegalBlockSizeException | KeyStoreException
@@ -1933,7 +1942,7 @@
         if (iv.length != PROFILE_KEY_IV_SIZE) {
             throw new IllegalArgumentException("Invalid iv length: " + iv.length);
         }
-        mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
+        mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
     }
 
     private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
@@ -2045,7 +2054,7 @@
                 LockscreenCredential piUserDecryptedPassword = profileUserDecryptedPasswords.get(i);
                 if (piUserId != -1 && piUserDecryptedPassword != null) {
                     if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
-                    tieProfileLockToParent(piUserId, piUserDecryptedPassword);
+                    tieProfileLockToParent(piUserId, userId, piUserDecryptedPassword);
                 }
                 if (piUserDecryptedPassword != null) {
                     piUserDecryptedPassword.zeroize();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 93b0dcb..41592bd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4410,10 +4410,9 @@
             pw.println("            " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]);
         }
         pw.println("      --reset: restore package to its post-install state");
-        pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
+        pw.println("      --check-prof (true | false): ignored - this is always true");
         pw.println("      --secondary-dex: compile app secondary dex files");
         pw.println("      --split SPLIT: compile only the given split name");
-        pw.println("      --compile-layouts: compile layout resources for faster inflation");
         pw.println("");
         pw.println("  force-dex-opt PACKAGE");
         pw.println("    Force immediate execution of dex opt for the given PACKAGE.");
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c6411ff..b7c3b97 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -119,10 +119,9 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.security.FileIntegrity;
 import com.android.server.uri.UriGrantsManagerInternal;
 
-import libcore.io.IoUtils;
-
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -207,6 +206,10 @@
     @VisibleForTesting
     static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
 
+    @VisibleForTesting
+    static final String FILENAME_USER_PACKAGES_RESERVE_COPY =
+            FILENAME_USER_PACKAGES + ".reservecopy";
+
     static final String DIRECTORY_BITMAPS = "bitmaps";
 
     private static final String TAG_ROOT = "root";
@@ -1055,6 +1058,11 @@
         return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
     }
 
+    @VisibleForTesting
+    final File getReserveCopyUserFile(@UserIdInt int userId) {
+        return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY);
+    }
+
     @GuardedBy("mLock")
     private void saveUserLocked(@UserIdInt int userId) {
         final File path = getUserFile(userId);
@@ -1062,6 +1070,9 @@
             Slog.d(TAG, "Saving to " + path);
         }
 
+        final File reservePath = getReserveCopyUserFile(userId);
+        reservePath.delete();
+
         path.getParentFile().mkdirs();
         final AtomicFile file = new AtomicFile(path);
         FileOutputStream os = null;
@@ -1079,6 +1090,23 @@
             file.failWrite(os);
         }
 
+        // Store the reserve copy of the file.
+        try (FileInputStream in = new FileInputStream(path);
+             FileOutputStream out = new FileOutputStream(reservePath)) {
+            FileUtils.copy(in, out);
+            FileUtils.sync(out);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write reserve copy: " + path, e);
+        }
+
+        // Protect both primary and reserve copy with fs-verity.
+        try {
+            FileIntegrity.setUpFsVerity(path);
+            FileIntegrity.setUpFsVerity(reservePath);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to verity-protect", e);
+        }
+
         getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
     }
 
@@ -1117,26 +1145,25 @@
         if (DEBUG || DEBUG_REBOOT) {
             Slog.d(TAG, "Loading from " + path);
         }
-        final AtomicFile file = new AtomicFile(path);
 
-        final FileInputStream in;
-        try {
-            in = file.openRead();
+        try (FileInputStream in = new AtomicFile(path).openRead()) {
+            return loadUserInternal(userId, in, /* forBackup= */ false);
         } catch (FileNotFoundException e) {
             if (DEBUG || DEBUG_REBOOT) {
                 Slog.d(TAG, "Not found " + path);
             }
-            return null;
+        } catch (Exception e) {
+            final File reservePath = getReserveCopyUserFile(userId);
+            Slog.e(TAG, "Reading from reserve copy: " + reservePath, e);
+            try (FileInputStream in = new AtomicFile(reservePath).openRead()) {
+                return loadUserInternal(userId, in, /* forBackup= */ false);
+            } catch (Exception exceptionReadingReserveFile) {
+                Slog.e(TAG, "Failed to read reserve copy: " + reservePath,
+                        exceptionReadingReserveFile);
+            }
+            Slog.e(TAG, "Failed to read file " + path, e);
         }
-        try {
-            final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
-            return ret;
-        } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
-            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
-            return null;
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
+        return null;
     }
 
     private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 07d36b3..18eebe4 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Intent;
@@ -39,6 +40,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -63,6 +65,9 @@
 import java.util.function.Predicate;
 
 public final class SuspendPackageHelper {
+
+    private static final String SYSTEM_EXEMPT_FROM_SUSPENSION = "system_exempt_from_suspension";
+
     // TODO(b/198166813): remove PMS dependency
     private final PackageManagerService mPm;
     private final PackageManagerServiceInjector mInjector;
@@ -502,6 +507,10 @@
             final String requiredPermissionControllerPackage =
                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
                             userId);
+            final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
+            final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                    SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
                 final String packageName = packageNames[i];
@@ -558,6 +567,7 @@
                 PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
                 AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
                 if (pkg != null) {
+                    final int uid = UserHandle.getUid(userId, packageState.getAppId());
                     // Cannot suspend SDK libs as they are controlled by SDK manager.
                     if (pkg.isSdkLibrary()) {
                         Slog.w(TAG, "Cannot suspend package: " + packageName
@@ -574,6 +584,13 @@
                                 + pkg.getStaticSharedLibraryName());
                         continue;
                     }
+                    if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow(
+                            AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
+                            == AppOpsManager.MODE_ALLOWED) {
+                        Slog.w(TAG, "Cannot suspend package \"" + packageName
+                                + "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set");
+                        continue;
+                    }
                 }
                 if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
                     Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a24f129..37877cf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -89,6 +89,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
@@ -160,6 +161,7 @@
 import android.service.vr.IPersistentVrStateCallbacks;
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableBoolean;
@@ -398,7 +400,6 @@
     ActivityTaskManagerInternal mActivityTaskManagerInternal;
     AutofillManagerInternal mAutofillManagerInternal;
     InputManagerInternal mInputManagerInternal;
-    InputMethodManagerInternal mInputMethodManagerInternal;
     DreamManagerInternal mDreamManagerInternal;
     PowerManagerInternal mPowerManagerInternal;
     IStatusBarService mStatusBarService;
@@ -659,6 +660,7 @@
     private static final int MSG_HANDLE_ALL_APPS = 22;
     private static final int MSG_LAUNCH_ASSIST = 23;
     private static final int MSG_RINGER_TOGGLE_CHORD = 24;
+    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -729,6 +731,9 @@
                 case MSG_SCREENSHOT_CHORD:
                     handleScreenShot(msg.arg1);
                     break;
+                case MSG_SWITCH_KEYBOARD_LAYOUT:
+                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                    break;
             }
         }
     }
@@ -1025,14 +1030,8 @@
                     break;
                 case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
                     if (mDismissImeOnBackKeyPressed) {
-                        if (mInputMethodManagerInternal == null) {
-                            mInputMethodManagerInternal =
-                                    LocalServices.getService(InputMethodManagerInternal.class);
-                        }
-                        if (mInputMethodManagerInternal != null) {
-                            mInputMethodManagerInternal.hideCurrentInputMethod(
+                        InputMethodManagerInternal.get().hideCurrentInputMethod(
                                     SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME);
-                        }
                     } else {
                         shortPressPowerGoHome();
                     }
@@ -2978,15 +2977,8 @@
                 break;
             case KeyEvent.KEYCODE_N:
                 if (down && event.isMetaPressed()) {
-                    IStatusBarService service = getStatusBarService();
-                    if (service != null) {
-                        try {
-                            service.expandNotificationsPanel();
-                        } catch (RemoteException e) {
-                            // do nothing.
-                        }
-                        return key_consumed;
-                    }
+                    toggleNotificationPanel();
+                    return key_consumed;
                 }
                 break;
             case KeyEvent.KEYCODE_S:
@@ -3177,7 +3169,7 @@
                 }
                 if (down && repeatCount == 0) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                    mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+                    sendSwitchKeyboardLayout(event, direction);
                     return key_consumed;
                 }
                 break;
@@ -3427,7 +3419,7 @@
                     if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
                             KeyEvent.META_CTRL_ON)) {
                         int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                        mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+                        sendSwitchKeyboardLayout(event, direction);
                         return true;
                     }
                 }
@@ -3453,6 +3445,19 @@
         return false;
     }
 
+    private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) {
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(),
+                direction).sendToTarget();
+    }
+
+    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
+            InputMethodManagerInternal.get().switchKeyboardLayout(direction);
+        } else {
+            mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction);
+        }
+    }
+
     private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
             int policyFlags) {
         int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index 7feb85f..daa0211 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1 +1,3 @@
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index ed3248ec..de631bb 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -181,24 +181,6 @@
     }
 
     /**
-     * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
-     *
-     * <p>This operation takes place in the calling thread rather than the service's handler thread.
-     */
-    @RequiresPermission(android.Manifest.permission.SET_TIME)
-    void clearTimeForTests() {
-        mContext.enforceCallingPermission(
-                android.Manifest.permission.SET_TIME, "clear latest network time");
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mNtpTrustedTime.clearCachedTimeResult();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
      * Forces the service to refresh the NTP time.
      *
      * <p>This operation takes place in the calling thread rather than the service's handler thread.
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
index afc0bdd..cfc95df 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
@@ -37,11 +37,6 @@
     private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
 
     /**
-     * A shell command that clears the time signal received from the network.
-     */
-    private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
-
-    /**
      * A shell command that forces the time signal to be refreshed from the network.
      */
     private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
@@ -73,8 +68,6 @@
         }
 
         switch (cmd) {
-            case SHELL_COMMAND_CLEAR_TIME:
-                return runClearTime();
             case SHELL_COMMAND_FORCE_REFRESH:
                 return runForceRefresh();
             case SHELL_COMMAND_SET_SERVER_CONFIG:
@@ -87,11 +80,6 @@
         }
     }
 
-    private int runClearTime() {
-        mNetworkTimeUpdateService.clearTimeForTests();
-        return 0;
-    }
-
     private int runForceRefresh() {
         boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
         getOutPrintWriter().println(success);
@@ -147,8 +135,6 @@
         pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
         pw.printf("  help\n");
         pw.printf("    Print this help text.\n");
-        pw.printf("  %s\n", SHELL_COMMAND_CLEAR_TIME);
-        pw.printf("    Clears the latest time.\n");
         pw.printf("  %s\n", SHELL_COMMAND_FORCE_REFRESH);
         pw.printf("    Refreshes the latest time. Prints whether it was successful.\n");
         pw.printf("  %s\n", SHELL_COMMAND_SET_SERVER_CONFIG);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 0da967a..22f096b 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -37,6 +37,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.NtpTrustedTime;
@@ -53,6 +54,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.InetSocketAddress;
 import java.time.DateTimeException;
 import java.util.Objects;
 
@@ -377,7 +379,7 @@
      *
      * <p>This operation takes place in the calling thread.
      */
-    void clearNetworkTime() {
+    void clearLatestNetworkTime() {
         enforceSuggestNetworkTimePermission();
 
         final long token = Binder.clearCallingIdentity();
@@ -390,12 +392,29 @@
 
     @Override
     public UnixEpochTime latestNetworkTime() {
-        NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion();
-        if (suggestion != null) {
-            return suggestion.getUnixEpochTime();
+        NetworkTimeSuggestion latestNetworkTime;
+        // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+        //  NtpTrustedTime result in a suggestion being made to the time detector.
+        //  mNtpTrustedTime can be removed once this happens.
+        if (TimeDetectorNetworkTimeHelper.isInUse()) {
+            // The new implementation.
+            latestNetworkTime = mTimeDetectorStrategy.getLatestNetworkSuggestion();
         } else {
+            // The old implementation.
+            NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+            if (ntpResult != null) {
+                latestNetworkTime = new NetworkTimeSuggestion(
+                        new UnixEpochTime(
+                                ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis()),
+                        ntpResult.getUncertaintyMillis());
+            } else {
+                latestNetworkTime = null;
+            }
+        }
+        if (latestNetworkTime == null) {
             throw new ParcelableException(new DateTimeException("Missing network time fix"));
         }
+        return latestNetworkTime.getUnixEpochTime();
     }
 
     /**
@@ -403,23 +422,7 @@
      */
     @Nullable
     NetworkTimeSuggestion getLatestNetworkSuggestion() {
-        // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
-        //  be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
-        //  detector. mNtpTrustedTime can be removed once this happens.
-        if (TimeDetectorNetworkTimeHelper.isInUse()) {
-            // The new implementation.
-            return mTimeDetectorStrategy.getLatestNetworkSuggestion();
-        } else {
-            // The old implementation.
-            NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
-            if (ntpResult != null) {
-                UnixEpochTime unixEpochTime = new UnixEpochTime(
-                        ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
-                return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
-            } else {
-                return null;
-            }
-        }
+        return mTimeDetectorStrategy.getLatestNetworkSuggestion();
     }
 
     /**
@@ -440,6 +443,57 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
     }
 
+    /**
+     * Sets the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+     *
+     * <p>This operation takes place in the calling thread.
+     */
+    void setNetworkTimeForSystemClockForTests(
+            @NonNull UnixEpochTime unixEpochTime, int uncertaintyMillis) {
+        enforceSuggestNetworkTimePermission();
+
+        // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+        //  NtpTrustedTime result in a suggestion being made to the time detector.
+        //  mNtpTrustedTime can be removed once this happens.
+        if (TimeDetectorNetworkTimeHelper.isInUse()) {
+            NetworkTimeSuggestion suggestion =
+                    new NetworkTimeSuggestion(unixEpochTime, uncertaintyMillis);
+            suggestion.addDebugInfo("Injected for tests");
+            mTimeDetectorStrategy.suggestNetworkTime(suggestion);
+        } else {
+            NtpTrustedTime.TimeResult timeResult = new NtpTrustedTime.TimeResult(
+                    unixEpochTime.getUnixEpochTimeMillis(),
+                    unixEpochTime.getElapsedRealtimeMillis(),
+                    uncertaintyMillis,
+                    InetSocketAddress.createUnresolved("time.set.for.tests", 123));
+            mNtpTrustedTime.setCachedTimeResult(timeResult);
+        }
+    }
+
+    /**
+     * Clears the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+     *
+     * <p>This operation takes place in the calling thread.
+     */
+    void clearNetworkTimeForSystemClockForTests() {
+        enforceSuggestNetworkTimePermission();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+            //  NtpTrustedTime result in a suggestion being made to the time detector.
+            //  mNtpTrustedTime can be removed once this happens.
+            if (TimeDetectorNetworkTimeHelper.isInUse()) {
+                // Clear the latest network suggestion. Done in all c
+                mTimeDetectorStrategy.clearLatestNetworkSuggestion();
+            } else {
+                mNtpTrustedTime.clearCachedTimeResult();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index cce5709..fe0127f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -16,12 +16,14 @@
 package com.android.server.timedetector;
 
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_NETWORK_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_NETWORK_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
@@ -73,9 +75,9 @@
             case SHELL_COMMAND_SUGGEST_NETWORK_TIME:
                 return runSuggestNetworkTime();
             case SHELL_COMMAND_GET_NETWORK_TIME:
-                return runGetNetworkTime();
+                return runGetLatestNetworkTime();
             case SHELL_COMMAND_CLEAR_NETWORK_TIME:
-                return runClearNetworkTime();
+                return runClearLatestNetworkTime();
             case SHELL_COMMAND_SUGGEST_GNSS_TIME:
                 return runSuggestGnssTime();
             case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
@@ -86,6 +88,10 @@
                 return runSetTimeState();
             case SHELL_COMMAND_CONFIRM_TIME:
                 return runConfirmTime();
+            case SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME:
+                return runClearSystemClockNetworkTime();
+            case SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME:
+                return runSetSystemClockNetworkTime();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -128,15 +134,15 @@
                 mInterface::suggestNetworkTime);
     }
 
-    private int runGetNetworkTime() {
+    private int runGetLatestNetworkTime() {
         NetworkTimeSuggestion networkTimeSuggestion = mInterface.getLatestNetworkSuggestion();
         final PrintWriter pw = getOutPrintWriter();
         pw.println(networkTimeSuggestion);
         return 0;
     }
 
-    private int runClearNetworkTime() {
-        mInterface.clearNetworkTime();
+    private int runClearLatestNetworkTime() {
+        mInterface.clearLatestNetworkTime();
         return 0;
     }
 
@@ -187,6 +193,20 @@
         return 0;
     }
 
+    private int runClearSystemClockNetworkTime() {
+        mInterface.clearNetworkTimeForSystemClockForTests();
+        return 0;
+    }
+
+    private int runSetSystemClockNetworkTime() {
+        NetworkTimeSuggestion networkTimeSuggestion =
+                NetworkTimeSuggestion.parseCommandLineArg(this);
+        mInterface.setNetworkTimeForSystemClockForTests(
+                networkTimeSuggestion.getUnixEpochTime(),
+                networkTimeSuggestion.getUncertaintyMillis());
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -218,6 +238,16 @@
         pw.printf("    Prints the network time information held by the detector.\n");
         pw.printf("  %s\n", SHELL_COMMAND_CLEAR_NETWORK_TIME);
         pw.printf("    Clears the network time information held by the detector.\n");
+        // TODO(b/222295093) Remove these "system_clock" commands when
+        //  SystemClock.currentNetworkTimeClock() is guaranteed to use the latest network
+        //  suggestion. Then, commands above can be used instead.
+        pw.printf("  %s <network suggestion opts>\n",
+                SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME);
+        pw.printf("    Sets the network time information used for"
+                + " SystemClock.currentNetworkTimeClock().\n");
+        pw.printf("  %s\n", SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME);
+        pw.printf("    Clears the network time information used for"
+                + " SystemClock.currentNetworkTimeClock().\n");
         pw.println();
         ManualTimeSuggestion.printCommandLineOpts(pw);
         pw.println();
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 91c4a2f..8a4fc0db 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -42,6 +42,9 @@
     // TODO(b/230590090): Replace with public documentation once ready
     static final String DOC_LINK = "go/android-asm";
 
+    /** Used to determine which version of the ASM logic was used in logs while we iterate */
+    static final int ASM_VERSION = 5;
+
     private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
     private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
     private static final String KEY_ASM_RESTRICTIONS_ENABLED = KEY_ASM_PREFIX
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 32dac49..e990460 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -60,7 +60,6 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.wm.ActivityRecord.State.FINISHING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -75,6 +74,7 @@
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_UID;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
@@ -83,6 +83,7 @@
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
+import static com.android.server.wm.BackgroundActivityStartController.balCodeToString;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
@@ -150,6 +151,9 @@
 import java.io.PrintWriter;
 import java.text.DateFormat;
 import java.util.Date;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -1967,8 +1971,7 @@
 
         // ASM rules have failed. Log why
         ActivityRecord targetTopActivity = targetTask == null ? null
-                : targetTask.getActivity(ar ->
-                        !ar.isState(FINISHING) && !ar.isAlwaysOnTop());
+                : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
 
         int action = newTask || mSourceRecord == null
                 ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
@@ -1999,7 +2002,7 @@
                 /* action */
                 action,
                 /* version */
-                4,
+                ActivitySecurityModelFeatureFlags.ASM_VERSION,
                 /* multi_window - we have our source not in the target task, but both are visible */
                 targetTask != null && mSourceRecord != null
                         && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(),
@@ -2011,22 +2014,26 @@
                     .shouldRestrictActivitySwitch(mCallingUid)
                 && shouldBlockActivityStart;
 
+        String launchedFromPackageName = r.launchedFromPackage;
         if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
+            String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
+                    + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+                    + getApplicationLabel(mService.mContext.getPackageManager(),
+                        launchedFromPackageName);
             UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
-                    "Activity start from " + r.launchedFromPackage
-                            + (blockActivityStartAndFeatureEnabled ? " " : " would be ")
-                            + "blocked by " + ActivitySecurityModelFeatureFlags.DOC_LINK,
-                    Toast.LENGTH_SHORT).show());
+                    toastText, Toast.LENGTH_LONG).show());
+
+            logDebugInfoForActivitySecurity("Launch", r, targetTask, targetTopActivity,
+                    blockActivityStartAndFeatureEnabled, /* taskToFront */ taskToFront);
         }
 
-
         if (blockActivityStartAndFeatureEnabled) {
-            Slog.e(TAG, "Abort Launching r: " + r
+            Slog.e(TAG, "[ASM] Abort Launching r: " + r
                     + " as source: "
-                    + (mSourceRecord != null ? mSourceRecord : r.launchedFromPackage)
+                    + (mSourceRecord != null ? mSourceRecord : launchedFromPackageName)
                     + " is in background. New task: " + newTask
                     + ". Top activity: " + targetTopActivity
-                    + ". BAL Code: " + mBalCode);
+                    + ". BAL Code: " + balCodeToString(mBalCode));
 
             return false;
         }
@@ -2034,6 +2041,71 @@
         return true;
     }
 
+    /** Only called when an activity launch may be blocked, which should happen very rarely */
+    private void logDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask,
+            ActivityRecord targetTopActivity, boolean blockActivityStartAndFeatureEnabled,
+            boolean taskToFront) {
+        final String prefix = "[ASM] ";
+        Function<ActivityRecord, String> recordToString = (ar) -> {
+            if (ar == null) {
+                return null;
+            }
+            return (ar == mSourceRecord ? " [source]=> "
+                    : ar == targetTopActivity ? " [ top  ]=> "
+                            : ar == r ? " [target]=> "
+                                    : "         => ")
+                    + ar
+                    + " :: visible=" + ar.isVisible()
+                    + ", finishing=" + ar.isFinishing()
+                    + ", alwaysOnTop=" + ar.isAlwaysOnTop()
+                    + ", taskFragment=" + ar.getTaskFragment();
+        };
+
+        StringJoiner joiner = new StringJoiner("\n");
+        joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
+        joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
+        joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
+
+        boolean targetTaskMatchesSourceTask = targetTask != null
+                && mSourceRecord != null && mSourceRecord.getTask() == targetTask;
+
+        if (mSourceRecord == null) {
+            joiner.add(prefix + "Source Package: " + r.launchedFromPackage);
+            String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
+                    mRealCallingUid);
+            joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
+        } else {
+            joiner.add(prefix + "Source Record: " + recordToString.apply(mSourceRecord));
+            if (targetTaskMatchesSourceTask) {
+                joiner.add(prefix + "Source/Target Task: " + mSourceRecord.getTask());
+                joiner.add(prefix + "Source/Target Task Stack: ");
+            } else {
+                joiner.add(prefix + "Source Task: " + mSourceRecord.getTask());
+                joiner.add(prefix + "Source Task Stack: ");
+            }
+            mSourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
+                    ar -> joiner.add(prefix + recordToString.apply(ar)));
+        }
+
+        joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
+        if (!targetTaskMatchesSourceTask) {
+            joiner.add(prefix + "Target Task: " + targetTask);
+            if (targetTask != null) {
+                joiner.add(prefix + "Target Task Stack: ");
+                targetTask.forAllActivities((Consumer<ActivityRecord>)
+                        ar -> joiner.add(prefix + recordToString.apply(ar)));
+            }
+        }
+
+        joiner.add(prefix + "Target Record: " + recordToString.apply(r));
+        joiner.add(prefix + "Intent: " + mIntent);
+        joiner.add(prefix + "TaskToFront: " + taskToFront);
+        joiner.add(prefix + "BalCode: " + balCodeToString(mBalCode));
+
+        joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
+        Slog.i(TAG, joiner.toString());
+    }
+
     /**
      * Returns whether embedding of {@code starting} is allowed.
      *
@@ -2165,8 +2237,8 @@
             return;
         }
 
-        Predicate<ActivityRecord> isLaunchingOrLaunched = ar -> !ar.finishing && (ar.isUid(
-                startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid));
+        Predicate<ActivityRecord> isLaunchingOrLaunched = ar -> !ar.finishing
+                && (ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid));
 
         // Return early if we know for sure we won't need to clear any activities by just checking
         // the top activity.
@@ -2202,7 +2274,10 @@
                             ? "Top activities cleared by "
                             : "Top activities would be cleared by ")
                             + ActivitySecurityModelFeatureFlags.DOC_LINK,
-                    Toast.LENGTH_SHORT).show());
+                    Toast.LENGTH_LONG).show());
+
+            logDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop,
+                    shouldBlockActivityStart, /* taskToFront */ true);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ef47b6e..e463358 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -51,7 +51,6 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.wm.ActivityRecord.State.FINISHING;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
@@ -103,6 +102,7 @@
 import android.app.servertransaction.ResumeActivityItem;
 import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -1236,7 +1236,7 @@
 
     int getDeviceIdForDisplayId(int displayId) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY)  {
-            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+            return Context.DEVICE_ID_DEFAULT;
         }
         if (mVirtualDeviceManager == null) {
             mVirtualDeviceManager =
@@ -1636,50 +1636,6 @@
             // Prevent recursion.
             return;
         }
-        boolean shouldBlockActivitySwitchIfFeatureEnabled = false;
-        boolean wouldBlockActivitySwitchIgnoringFlags = false;
-        // We may have already checked that the callingUid has additional clearTask privileges, and
-        // cleared the calling identify. If so, we infer we do not need further restrictions here.
-        // TODO(b/263368846) Move to live with the rest of the ASM logic.
-        if (callingUid != SYSTEM_UID) {
-            Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task,
-                    callingUid,
-                    null);
-            shouldBlockActivitySwitchIfFeatureEnabled = !pair.first;
-            wouldBlockActivitySwitchIgnoringFlags = !pair.second;
-            if (wouldBlockActivitySwitchIgnoringFlags) {
-                ActivityRecord topActivity =  task.getActivity(ar ->
-                        !ar.isState(FINISHING) && !ar.isAlwaysOnTop());
-                FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
-                        /* caller_uid */
-                        callingUid,
-                        /* caller_activity_class_name */
-                        callerActivityClassName,
-                        /* target_task_top_activity_uid */
-                        topActivity == null ? -1 : topActivity.getUid(),
-                        /* target_task_top_activity_class_name */
-                        topActivity == null ? null : topActivity.info.name,
-                        /* target_task_is_different */
-                        false,
-                        /* target_activity_uid */
-                        -1,
-                        /* target_activity_class_name */
-                        null,
-                        /* target_intent_action */
-                        null,
-                        /* target_intent_flags */
-                        0,
-                        /* action */
-                        FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
-                        /* version */
-                        3,
-                        /* multi_window */
-                        false,
-                        /* bal_code */
-                        -1
-                );
-            }
-        }
         task.mTransitionController.requestCloseTransitionIfNeeded(task);
         task.mInRemoveTask = true;
         try {
@@ -1690,33 +1646,107 @@
             if (task.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
-            if (wouldBlockActivitySwitchIgnoringFlags) {
-                boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
-                        .shouldRestrictActivitySwitch(callingUid)
-                        && shouldBlockActivitySwitchIfFeatureEnabled;
-                if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
-                    UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
-                            (restrictActivitySwitch
-                                    ? "Returning home due to "
-                                    : "Would return home due to ")
-                                    + ActivitySecurityModelFeatureFlags.DOC_LINK,
-                            Toast.LENGTH_SHORT).show());
-                }
-
-                // If the activity switch should be restricted, return home rather than the
-                // previously top task, to prevent users from being confused which app they're
-                // viewing
-                if (restrictActivitySwitch) {
-                    Slog.w(TAG, "Return to home as source uid: " + callingUid
-                            + "is not on top of task t: " + task);
-                    task.getTaskDisplayArea().moveHomeActivityToTop("taskRemoved");
-                }
-            }
+            checkActivitySecurityForTaskClear(callingUid, task, callerActivityClassName);
         } finally {
             task.mInRemoveTask = false;
         }
     }
 
+    // TODO(b/263368846) Move to live with the rest of the ASM logic.
+    /**
+     * Returns home if the passed in callingUid is not top of the stack, rather than returning to
+     * previous task.
+     */
+    private void checkActivitySecurityForTaskClear(int callingUid, Task task,
+            String callerActivityClassName) {
+        // We may have already checked that the callingUid has additional clearTask privileges, and
+        // cleared the calling identify. If so, we infer we do not need further restrictions here.
+        if (callingUid == SYSTEM_UID) {
+            return;
+        }
+
+        TaskDisplayArea displayArea = task.getTaskDisplayArea();
+        if (displayArea == null) {
+            // If there is no associated display area, we can not return home.
+            return;
+        }
+
+        Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task, callingUid, null);
+        boolean shouldBlockActivitySwitchIfFeatureEnabled = !pair.first;
+        boolean wouldBlockActivitySwitchIgnoringFlags = !pair.second;
+
+        if (!wouldBlockActivitySwitchIgnoringFlags) {
+            return;
+        }
+
+        ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                /* caller_uid */
+                callingUid,
+                /* caller_activity_class_name */
+                callerActivityClassName,
+                /* target_task_top_activity_uid */
+                topActivity == null ? -1 : topActivity.getUid(),
+                /* target_task_top_activity_class_name */
+                topActivity == null ? null : topActivity.info.name,
+                /* target_task_is_different */
+                false,
+                /* target_activity_uid */
+                -1,
+                /* target_activity_class_name */
+                null,
+                /* target_intent_action */
+                null,
+                /* target_intent_flags */
+                0,
+                /* action */
+                FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+                /* version */
+                ActivitySecurityModelFeatureFlags.ASM_VERSION,
+                /* multi_window */
+                false,
+                /* bal_code */
+                -1
+        );
+
+        boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
+                .shouldRestrictActivitySwitch(callingUid)
+                && shouldBlockActivitySwitchIfFeatureEnabled;
+
+        PackageManager pm = mService.mContext.getPackageManager();
+        String callingPackage = pm.getNameForUid(callingUid);
+        final CharSequence callingLabel;
+        if (callingPackage == null) {
+            callingPackage = String.valueOf(callingUid);
+            callingLabel = callingPackage;
+        } else {
+            callingLabel = getApplicationLabel(pm, callingPackage);
+        }
+
+        if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+            Toast toast = Toast.makeText(mService.mContext,
+                    (ActivitySecurityModelFeatureFlags.DOC_LINK
+                            + (restrictActivitySwitch
+                            ? "returned home due to "
+                            : "would return home due to ")
+                            + callingLabel),
+                    Toast.LENGTH_LONG);
+            UiThread.getHandler().post(toast::show);
+        }
+
+        // If the activity switch should be restricted, return home rather than the
+        // previously top task, to prevent users from being confused which app they're
+        // viewing
+        if (restrictActivitySwitch) {
+            Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
+                    + " is not on top of task t: " + task);
+            displayArea.moveHomeActivityToTop("taskRemoved");
+        } else {
+            Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
+                    + " is not on top of task t: " + task);
+        }
+    }
+
     /**
      *  For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
      *  1. Which is top of the stack in z-order
@@ -1743,7 +1773,7 @@
         // Consider the source activity, whether or not it is finishing. Do not consider any other
         // finishing activity.
         Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
-                || (!ar.isState(FINISHING) && !ar.isAlwaysOnTop());
+                || (!ar.finishing && !ar.isAlwaysOnTop());
 
         // Check top of stack (or the first task fragment for embedding).
         ActivityRecord topActivity = task.getActivity(topOfStackPredicate);
@@ -1777,6 +1807,16 @@
         return topActivity.allowCrossUidActivitySwitchFromBelow(uid);
     }
 
+    static CharSequence getApplicationLabel(PackageManager pm, String packageName) {
+        try {
+            ApplicationInfo launchedFromPackageInfo = pm.getApplicationInfo(
+                    packageName, PackageManager.ApplicationInfoFlags.of(0));
+            return pm.getApplicationLabel(launchedFromPackageInfo);
+        } catch (PackageManager.NameNotFoundException e) {
+            return packageName;
+        }
+    }
+
     void cleanUpRemovedTaskLocked(Task task, boolean killProcess, boolean removeFromRecents) {
         if (removeFromRecents) {
             mRecentTasks.remove(task);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 587138d..8fc3797 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -114,6 +114,35 @@
     /** Process belongs to a SDK sandbox */
     static final int BAL_ALLOW_SDK_SANDBOX = 10;
 
+    static String balCodeToString(@BalCode int balCode) {
+        switch (balCode) {
+            case BAL_ALLOW_ALLOWLISTED_COMPONENT:
+                return "BAL_ALLOW_ALLOWLISTED_COMPONENT";
+            case BAL_ALLOW_ALLOWLISTED_UID:
+                return "BAL_ALLOW_ALLOWLISTED_UID";
+            case BAL_ALLOW_DEFAULT:
+                return "BAL_ALLOW_DEFAULT";
+            case BAL_ALLOW_FOREGROUND:
+                return "BAL_ALLOW_FOREGROUND";
+            case BAL_ALLOW_GRACE_PERIOD:
+                return "BAL_ALLOW_GRACE_PERIOD";
+            case BAL_ALLOW_PENDING_INTENT:
+                return "BAL_ALLOW_PENDING_INTENT";
+            case BAL_ALLOW_PERMISSION:
+                return "BAL_ALLOW_PERMISSION";
+            case BAL_ALLOW_SAW_PERMISSION:
+                return "BAL_ALLOW_SAW_PERMISSION";
+            case BAL_ALLOW_SDK_SANDBOX:
+                return "BAL_ALLOW_SDK_SANDBOX";
+            case BAL_ALLOW_VISIBLE_WINDOW:
+                return "BAL_ALLOW_VISIBLE_WINDOW";
+            case BAL_BLOCK:
+                return "BAL_BLOCK";
+            default:
+                throw new IllegalArgumentException("Unexpected value: " + balCode);
+        }
+    }
+
     BackgroundActivityStartController(
             final ActivityTaskManagerService service, final ActivityTaskSupervisor supervisor) {
         mService = service;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index e56b679..7c0318d 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -54,7 +54,6 @@
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
 import android.app.servertransaction.ConfigurationChangeItem;
-import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -211,7 +210,7 @@
     /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
     private volatile boolean mHasCachedConfiguration;
 
-    private int mTopActivityDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
+    private int mTopActivityDeviceId = Context.DEVICE_ID_DEFAULT;
     /**
      * Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
      * registered.
@@ -1435,7 +1434,7 @@
         // TODO(b/263402938): Add tests that capture the deviceId dispatch to the client.
         mTopActivityDeviceId = deviceId;
         dispatchConfiguration(config, topActivityDeviceChanged ? mTopActivityDeviceId
-                : VirtualDeviceManager.DEVICE_ID_INVALID);
+                : Context.DEVICE_ID_INVALID);
     }
 
     private int getTopActivityDeviceId() {
@@ -1520,7 +1519,7 @@
 
     private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
         // By default send invalid deviceId as no-op signal so it's not updated on the client side.
-        scheduleConfigurationChange(thread, config, VirtualDeviceManager.DEVICE_ID_INVALID);
+        scheduleConfigurationChange(thread, config, Context.DEVICE_ID_INVALID);
     }
 
     private void scheduleConfigurationChange(IApplicationThread thread, Configuration config,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 8b913db..7a4e7df 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.credentials;
 
 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;
 
@@ -631,7 +632,8 @@
             }
 
             // Send an intent to the UI that we have new enabled providers.
-            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
+            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
+                    LAUNCH_CREDENTIAL_SELECTOR);
         }
 
         @Override
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 5e16771..32b14d7 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -44,7 +44,6 @@
         IGetCredentialCallback>
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetRequestSession";
-
     public GetRequestSession(Context context, int userId, int callingUid,
             IGetCredentialCallback callback, GetCredentialRequest request,
             CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) {
@@ -173,6 +172,12 @@
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName) {
         Log.i(TAG, "in onStatusChanged with status: " + status);
+        // Auth entry was selected, and it did not have any underlying credentials
+        if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
+            handleEmptyAuthenticationSelection(componentName);
+            return;
+        }
+        // For any other status, we check if all providers are done and then invoke UI if needed
         if (!isAnyProviderPending()) {
             // If all provider responses have been received, we can either need the UI,
             // or we need to respond with error. The only other case is the entry being
@@ -186,4 +191,34 @@
             }
         }
     }
+
+    private void handleEmptyAuthenticationSelection(ComponentName componentName) {
+        // Update auth entry statuses across different provider sessions
+        mProviders.keySet().forEach(key -> {
+            ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
+            if (!session.mComponentName.equals(componentName)) {
+                session.updateAuthEntriesStatusFromAnotherSession();
+            }
+        });
+
+        // Invoke UI since it needs to show a snackbar if last auth entry, or a status on each
+        // auth entries along with other valid entries
+        getProviderDataAndInitiateUi();
+
+        // Respond to client if all auth entries are empty and nothing else to show on the UI
+        if (providerDataContainsEmptyAuthEntriesOnly()) {
+            respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+                    "No credentials available");
+        }
+    }
+
+    private boolean providerDataContainsEmptyAuthEntriesOnly() {
+        for (String key : mProviders.keySet()) {
+            ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
+            if (!session.containsEmptyAuthEntriesOnly()) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 12074c7..8c94b0a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -241,13 +241,14 @@
                 if (additionalContentReceived) {
                     Log.i(TAG, "Additional content received - removing authentication entry");
                     mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
+                    if (!mProviderResponseDataHandler.isEmptyResponse()) {
+                        updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+                    }
                 } else {
                     Log.i(TAG, "Additional content not received");
                     mProviderResponseDataHandler
                             .updateAuthEntryWithNoCredentialsReceived(entryKey);
-                }
-                if (!mProviderResponseDataHandler.isEmptyResponse()) {
-                    updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+                    updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY);
                 }
                 break;
             case REMOTE_ENTRY_KEY:
@@ -456,6 +457,27 @@
                 GetCredentialException.TYPE_UNKNOWN, null);
     }
 
+    /** Update auth entries status based on an auth entry selected from a different session. */
+    public void updateAuthEntriesStatusFromAnotherSession() {
+        // Pass null for entryKey if the auth entry selected belongs to a different session
+        mProviderResponseDataHandler.updateAuthEntryWithNoCredentialsReceived(/*entryKey=*/null);
+    }
+
+    /** Returns true if the provider response contains empty auth entries only, false otherwise. **/
+    public boolean containsEmptyAuthEntriesOnly() {
+        // We do not consider action entries here because if actions are the only entries,
+        // we don't show the UI
+        return mProviderResponseDataHandler.mUiCredentialEntries.isEmpty()
+                && mProviderResponseDataHandler.mUiRemoteEntry == null
+                && mProviderResponseDataHandler.mUiAuthenticationEntries
+                .values().stream().allMatch(
+                        e -> e.second.getStatus() == AuthenticationEntry
+                                .STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
+                                || e.second.getStatus()
+                                == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
+        );
+    }
+
     private class ProviderResponseDataHandler {
         private final ComponentName mExpectedRemoteEntryProviderService;
         @NonNull
@@ -610,7 +632,12 @@
                     ? null : mUiCredentialEntries.get(entryKey).first;
         }
 
-        public void updateAuthEntryWithNoCredentialsReceived(String entryKey) {
+        public void updateAuthEntryWithNoCredentialsReceived(@Nullable String entryKey) {
+            if (entryKey == null) {
+                // Auth entry from a different provider was selected by the user.
+                updatePreviousMostRecentAuthEntry();
+                return;
+            }
             updatePreviousMostRecentAuthEntry();
             updateMostRecentAuthEntry(entryKey);
         }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 2f9d578..ecddcf3 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -66,7 +66,8 @@
      * on the credMan UI.
      */
     public static boolean isUiInvokingStatus(Status status) {
-        return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
+        return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
+                || status == Status.NO_CREDENTIALS_FROM_AUTH_ENTRY;
     }
 
     /**
@@ -140,7 +141,7 @@
         PENDING_INTENT_INVOKED,
         CREDENTIAL_RECEIVED_FROM_SELECTION,
         SAVE_ENTRIES_RECEIVED, CANCELED,
-        NO_CREDENTIALS, EMPTY_RESPONSE, COMPLETE
+        NO_CREDENTIALS, EMPTY_RESPONSE, NO_CREDENTIALS_FROM_AUTH_ENTRY, COMPLETE
     }
 
     /** Converts exception to a provider session status. */
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index f0d05c5..f8bbfcf 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -216,7 +216,7 @@
      * Returns true if at least one provider is ready for UI invocation, and no
      * provider is pending a response.
      */
-    boolean isUiInvocationNeeded() {
+    protected boolean isUiInvocationNeeded() {
         for (ProviderSession session : mProviders.values()) {
             if (ProviderSession.isUiInvokingStatus(session.getStatus())) {
                 return true;
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 05a8b11..07ddda3 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -52,6 +52,10 @@
         "android.test.runner",
     ],
 
+    data: [
+        ":SimpleTestIme",
+    ],
+
     certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
index 92be780..1371934 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
@@ -21,6 +21,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
         <option name="test-file-name" value="FrameworksInputMethodSystemServerTests.apk" />
+        <option name="test-file-name" value="SimpleTestIme.apk" />
     </target_preparer>
 
     <option name="test-tag" value="FrameworksInputMethodSystemServerTests" />
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
new file mode 100644
index 0000000..7cbfc52
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodManagerServiceRestrictImeAmountTest extends
+        InputMethodManagerServiceTestBase {
+
+    @Test
+    public void testFilterInputMethodServices_loadsAllImesBelowThreshold() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertEquals(5, methodList.size());
+    }
+
+    @Test
+    public void testFilterInputMethodServices_ignoresImesBeyondThreshold() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    @Test
+    public void testFilterInputMethodServices_loadsSystemImesBeyondThreshold() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeSystemResolveInfo("com.android.apps.inputmethod.systemime",
+                            "SystemIME" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                2 * InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    @Test
+    public void testFilterInputMethodServices_ignoresImesBeyondThresholdFromTwoPackages() {
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime1", "IME1_" + i));
+        }
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime2", "IME2_" + i));
+        }
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of());
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                2 * InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    @Test
+    public void testFilterInputMethodServices_stillLoadsEnabledImesBeyondThreshold() {
+        final ResolveInfo enabledIme = createFakeResolveInfo(
+                "com.android.apps.inputmethod.simpleime_enabled", "EnabledIME");
+
+        List<ResolveInfo> resolveInfoList = new ArrayList<>();
+        for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) {
+            resolveInfoList.add(
+                    createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i));
+        }
+        resolveInfoList.add(enabledIme);
+
+        final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList,
+                List.of(new ComponentName(enabledIme.serviceInfo.packageName,
+                        enabledIme.serviceInfo.name).flattenToShortString()));
+
+        assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo(
+                1 + InputMethodInfo.MAX_IMES_PER_PACKAGE);
+    }
+
+    private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
+            List<String> enabledComponents) {
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+        InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList,
+                enabledComponents, mContext, resolveInfoList);
+        return methodList;
+    }
+
+    private ResolveInfo createFakeSystemResolveInfo(String packageName, String componentName) {
+        final ResolveInfo ime = createFakeResolveInfo(packageName, componentName);
+        ime.serviceInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        return ime;
+    }
+
+    private ResolveInfo createFakeResolveInfo(String packageName, String componentName) {
+        final ResolveInfo ime = getResolveInfo("com.android.apps.inputmethod.simpleime");
+        if (packageName != null) {
+            ime.serviceInfo.packageName = packageName;
+        }
+        if (componentName != null) {
+            ime.serviceInfo.name = componentName;
+        }
+        return ime;
+    }
+
+    private ResolveInfo getResolveInfo(String packageName) {
+        final int flags = PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        final List<ResolveInfo> ime = mContext.getPackageManager().queryIntentServices(
+                new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
+                PackageManager.ResolveInfoFlags.of(flags));
+        assertWithMessage("Loaded IMEs").that(ime.size()).isGreaterThan(0);
+        return ime.get(0);
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index dbdffd0..9501b96 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -127,7 +127,7 @@
                 mockitoSession()
                         .initMocks(this)
                         .strictness(Strictness.LENIENT)
-                        .mockStatic(LocalServices.class)
+                        .spyStatic(LocalServices.class)
                         .mockStatic(ServiceManager.class)
                         .mockStatic(SystemServerInitThreadPool.class)
                         .startMocking();
@@ -212,6 +212,7 @@
                 new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService);
 
         // Public local InputMethodManagerService.
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         lifecycle.onStart();
         try {
             // After this boot phase, services can broadcast Intents.
@@ -237,6 +238,7 @@
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
     }
 
     protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
new file mode 100644
index 0000000..111cabd
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase {
+    @Test
+    public void testSwitchToNextKeyboardLayout() {
+        ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController);
+        InputMethodManagerInternal.get().switchKeyboardLayout(1);
+        verify(mInputMethodManagerService.mSwitchingController)
+                .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any());
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 991d566..8b420a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -234,15 +234,9 @@
                 createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
         JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
                 createJobInfo(6));
-        JobStatus jobDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
-                createJobInfo(7)
-                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
-        JobStatus jobUI = createJobStatus("testGetMinJobExecutionGuaranteeMs",
-                createJobInfo(8)); // TODO(255371817): add setUserInitiated(true)
         JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
-                // TODO(255371817): add setUserInitiated(true)
                 createJobInfo(9)
-                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+                        .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
 
         spyOn(ejMax);
         spyOn(ejHigh);
@@ -250,8 +244,6 @@
         spyOn(ejHighDowngraded);
         spyOn(jobHigh);
         spyOn(jobDef);
-        spyOn(jobDT);
-        spyOn(jobUI);
         spyOn(jobUIDT);
 
         when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
@@ -260,14 +252,11 @@
         when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
         when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
         when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
-        when(jobUI.shouldTreatAsUserInitiatedJob()).thenReturn(true);
         when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true);
 
         ConnectivityController connectivityController = mService.getConnectivityController();
         spyOn(connectivityController);
         mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
-        mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 15 * MINUTE_IN_MILLIS;
-        mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
         mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
         mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
         mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
@@ -284,37 +273,14 @@
                 mService.getMinJobExecutionGuaranteeMs(jobHigh));
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDef));
-        grantRunUserInitiatedJobsPermission(false); // Without permission
-        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobDT));
-        grantRunUserInitiatedJobsPermission(true); // With permission
-        doReturn(ConnectivityController.UNKNOWN_TIME)
-                .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobDT));
-        doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS / 2)
-                .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobDT));
-        doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS * 2)
-                .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobDT));
-        doReturn(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS * 2)
-                .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobDT));
         // UserInitiated
         grantRunUserInitiatedJobsPermission(false);
-        // Permission isn't granted, so it should just be treated as a regular data transfer job.
-        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
         // Permission isn't granted, so it should just be treated as a regular job.
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobUI));
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
         grantRunUserInitiatedJobsPermission(true); // With permission
-        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
-                mService.getMinJobExecutionGuaranteeMs(jobUI));
+        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
         doReturn(ConnectivityController.UNKNOWN_TIME)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
         assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
@@ -338,21 +304,10 @@
 
     @Test
     public void testGetMaxJobExecutionTimeMs() {
-        JobStatus jobDT = createJobStatus("testGetMaxJobExecutionTimeMs",
-                createJobInfo(7)
-                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
-        JobStatus jobUI = createJobStatus("testGetMaxJobExecutionTimeMs",
-                createJobInfo(9)); // TODO(255371817): add setUserInitiated(true)
         JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
-                // TODO(255371817): add setUserInitiated(true)
                 createJobInfo(10)
-                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
-
-        spyOn(jobDT);
-        spyOn(jobUI);
+                        .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
         spyOn(jobUIDT);
-
-        when(jobUI.shouldTreatAsUserInitiatedJob()).thenReturn(true);
         when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true);
 
         QuotaController quotaController = mService.getQuotaController();
@@ -365,17 +320,9 @@
                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
 
         grantRunUserInitiatedJobsPermission(true);
-        assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
-                mService.getMaxJobExecutionTimeMs(jobDT));
-        assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_LIMIT_MS,
-                mService.getMaxJobExecutionTimeMs(jobUI));
         assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobUIDT));
         grantRunUserInitiatedJobsPermission(false);
-        assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
-                mService.getMaxJobExecutionTimeMs(jobDT));
-        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
-                mService.getMaxJobExecutionTimeMs(jobUI));
         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobUIDT));
     }
@@ -478,7 +425,8 @@
     @Test
     public void testGetRescheduleJobForFailure_userStopped() {
         JobStatus uiJob = createJobStatus("testGetRescheduleJobForFailure",
-                createJobInfo().setUserInitiated(true));
+                createJobInfo().setUserInitiated(true)
+                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
         JobStatus uvJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
         spyOn(uvJob);
         doReturn(true).when(uvJob).isUserVisibleJob();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 1e65b38..6bc552c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -619,7 +619,7 @@
     @Test
     public void testExceptions_UserInitiated() {
         JobInfo.Builder jb = createJob(0);
-        jb.setUserInitiated(true);
+        jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
         JobStatus js = createJobStatus("testExceptions_UserInitiated", jb);
         assertFalse(js.hasFlexibilityConstraint());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index e6bc72f..5dc8ed5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -170,6 +170,7 @@
         final JobInfo jobInfo =
                 new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                         .setUserInitiated(true)
+                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build();
         JobStatus job = createJobStatus(jobInfo);
         assertTrue(job.canRunInBatterySaver());
@@ -216,6 +217,7 @@
         final JobInfo jobInfo =
                 new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                         .setUserInitiated(true)
+                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build();
         JobStatus job = createJobStatus(jobInfo);
         assertTrue(job.canRunInDoze());
@@ -236,6 +238,7 @@
         // User-initiated jobs are always user-visible unless they've been demoted.
         jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                 .setUserInitiated(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                 .build();
         job = createJobStatus(jobInfo);
 
@@ -507,6 +510,7 @@
 
         jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                 .setUserInitiated(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                 .build();
         job = createJobStatus(jobInfo);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index b0c3a6e..e9d8269 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -37,9 +37,12 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.hardware.biometrics.AuthenticateOptions;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.Fingerprint;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -48,6 +51,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
+import android.util.Slog;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -58,6 +62,8 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
 import com.android.server.biometrics.nano.BiometricsProto;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -67,6 +73,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.function.Supplier;
 
 @Presubmit
@@ -78,6 +87,9 @@
     private static final String TAG = "BiometricSchedulerTest";
     private static final int TEST_SENSOR_ID = 1;
     private static final int LOG_NUM_RECENT_OPERATIONS = 2;
+    private static final Fingerprint TEST_FINGERPRINT = new Fingerprint("" /* name */,
+            1 /* fingerId */, TEST_SENSOR_ID);
+
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getContext(), null);
@@ -673,6 +685,56 @@
 
     }
 
+    @Test
+    public void testTwoInternalCleanupOps_withFirstFavorHalEnrollment() throws Exception {
+        final String owner = "test.owner";
+        final int userId = 1;
+        final Supplier<Object> daemon = () -> mock(AidlSession.class);
+        final FingerprintUtils utils = mock(FingerprintUtils.class);
+        final Map<Integer, Long> authenticatorIds = new HashMap<>();
+        final ClientMonitorCallback callback0 = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
+
+        final TestInternalCleanupClient client1 = new
+                TestInternalCleanupClient(mContext, daemon, userId,
+                owner, TEST_SENSOR_ID, mock(BiometricLogger.class),
+                mBiometricContext, utils, authenticatorIds);
+        final TestInternalCleanupClient client2 = new
+                TestInternalCleanupClient(mContext, daemon, userId,
+                owner, TEST_SENSOR_ID, mock(BiometricLogger.class),
+                mBiometricContext, utils, authenticatorIds);
+
+        //add initial start client to scheduler, so later clients will be on pending operation queue
+        final TestHalClientMonitor startClient = new TestHalClientMonitor(mContext, mToken,
+                daemon);
+        mScheduler.scheduleClientMonitor(startClient, callback0);
+
+        //add first cleanup client which favors enrollments from HAL
+        client1.setFavorHalEnrollments();
+        mScheduler.scheduleClientMonitor(client1, callback1);
+        assertEquals(1, mScheduler.mPendingOperations.size());
+
+        when(utils.getBiometricsForUser(mContext, userId)).thenAnswer(i ->
+                new ArrayList<>(client1.getFingerprints()));
+
+        //add second cleanup client
+        mScheduler.scheduleClientMonitor(client2, callback2);
+
+        //finish the start client, so other pending clients are processed
+        startClient.getCallback().onClientFinished(startClient, true);
+
+        waitForIdle();
+
+        assertTrue(client1.isAlreadyDone());
+        assertTrue(client2.isAlreadyDone());
+        assertNull(mScheduler.mCurrentOperation);
+        verify(utils, never()).removeBiometricForUser(mContext, userId,
+                TEST_FINGERPRINT.getBiometricId());
+        assertEquals(1,  client1.getFingerprints().size());
+    }
+
+
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
     }
@@ -681,7 +743,37 @@
         TestableLooper.get(this).processAllMessages();
     }
 
-    private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+    private static class TestAuthenticateOptions implements AuthenticateOptions {
+        @Override
+        public int getUserId() {
+            return 0;
+        }
+
+        @Override
+        public int getSensorId() {
+            return TEST_SENSOR_ID;
+        }
+
+        @Override
+        public int getDisplayState() {
+            return DISPLAY_STATE_UNKNOWN;
+        }
+
+        @NonNull
+        @Override
+        public String getOpPackageName() {
+            return "some.test.name";
+        }
+
+        @Nullable
+        @Override
+        public String getAttributionTag() {
+            return null;
+        }
+    }
+
+    private static class TestAuthenticationClient
+            extends AuthenticationClient<Object, TestAuthenticateOptions> {
         boolean mStartedHal = false;
         boolean mStoppedHal = false;
         boolean mDestroyed = false;
@@ -700,9 +792,10 @@
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter listener, int cookie,
                 @NonNull BiometricContext biometricContext) {
-            super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
-                    false /* restricted */, TAG, cookie, false /* requireConfirmation */,
-                    TEST_SENSOR_ID, mock(BiometricLogger.class), biometricContext,
+            super(context, lazyDaemon, token, listener, 0 /* operationId */,
+                    false /* restricted */, new TestAuthenticateOptions(), cookie,
+                    false /* requireConfirmation */,
+                    mock(BiometricLogger.class), biometricContext,
                     true /* isStrongBiometric */, null /* taskStackListener */,
                     null /* lockoutTracker */, false /* isKeyguard */,
                     true /* shouldVibrate */,
@@ -835,4 +928,90 @@
             mDestroyed = true;
         }
     }
+
+    private static class TestInternalEnumerateClient extends InternalEnumerateClient<Object> {
+        private static final String TAG = "TestInternalEnumerateClient";
+
+
+        protected TestInternalEnumerateClient(@NonNull Context context,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, int userId,
+                @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
+                @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+            super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
+                    logger, biometricContext);
+        }
+
+        @Override
+        protected void startHalOperation() {
+            Slog.d(TAG, "TestInternalEnumerateClient#startHalOperation");
+            onEnumerationResult(TEST_FINGERPRINT, 0 /* remaining */);
+        }
+    }
+
+    private static class TestRemovalClient extends RemovalClient<Fingerprint, Object> {
+        private static final String TAG = "TestRemovalClient";
+
+        TestRemovalClient(@NonNull Context context,
+                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
+                @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
+                @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+                @NonNull Map<Integer, Long> authenticatorIds) {
+            super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
+                    logger, biometricContext, authenticatorIds);
+        }
+
+        @Override
+        protected void startHalOperation() {
+            Slog.d(TAG, "Removing template from hw");
+            onRemoved(TEST_FINGERPRINT, 0);
+        }
+    }
+
+    private static class TestInternalCleanupClient extends
+            InternalCleanupClient<Fingerprint, Object> {
+        private List<Fingerprint> mFingerprints = new ArrayList<>();
+
+        TestInternalCleanupClient(@NonNull Context context,
+                @NonNull Supplier<Object> lazyDaemon,
+                int userId, @NonNull String owner, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+                @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
+            super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+                    utils, authenticatorIds);
+        }
+
+        @Override
+        protected InternalEnumerateClient<Object> getEnumerateClient(Context context,
+                Supplier<Object> lazyDaemon, IBinder token, int userId, String owner,
+                List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+            return new TestInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
+                    enrolledList, utils, sensorId,
+                    logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE),
+                    biometricContext);
+        }
+
+        @Override
+        protected RemovalClient<Fingerprint, Object> getRemovalClient(Context context,
+                Supplier<Object> lazyDaemon, IBinder token, int biometricId, int userId,
+                String owner, BiometricUtils<Fingerprint> utils, int sensorId,
+                @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+                Map<Integer, Long> authenticatorIds) {
+            return new TestRemovalClient(context, lazyDaemon, token, null,
+                    new int[]{biometricId}, userId, owner, utils, sensorId, logger,
+                    biometricContext, authenticatorIds);
+        }
+
+        @Override
+        protected void onAddUnknownTemplate(int userId,
+                @NonNull BiometricAuthenticator.Identifier identifier) {
+            mFingerprints.add((Fingerprint) identifier);
+        }
+
+        public List<Fingerprint> getFingerprints() {
+            return mFingerprints;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 184a556..3ff802c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -153,14 +154,18 @@
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
+                .setOpPackageName("test-owner")
+                .setUserId(5)
+                .setSensorId(9)
+                .build();
         return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
-                2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
-                false /* restricted */, "test-owner", 4 /* cookie */,
-                false /* requireConfirmation */, 9 /* sensorId */,
+                2 /* requestId */, mClientMonitorCallbackConverter, OP_ID,
+                false /* restricted */, options, 4 /* cookie */,
+                false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
                 mUsageStats, null /* mLockoutCache */, false /* allowBackgroundAuthentication */,
-                null /* sensorPrivacyManager */,
-                0 /* biometricStrength */) {
+                null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index e0fdb8c..c4c5505 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.biometrics.face.ISession;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -112,8 +113,13 @@
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
         return new FaceDetectClient(mContext, () -> aidl, mToken,
-                99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
-                "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+                99 /* requestId */, mClientMonitorCallbackConverter,
+                new FaceAuthenticateOptions.Builder()
+                        .setUserId(USER_ID)
+                        .setSensorId(5)
+                        .setOpPackageName("own-it")
+                        .build(),
+                mBiometricLogger, mBiometricContext,
                 false /* isStrongBiometric */, null /* sensorPrivacyManager */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 3b66eab..54d6478 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -144,7 +144,7 @@
 
     @Test
     public void testAuthentication_enrollmentCallbackNeverNotified() {
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
+        AuthenticationClient<?, ?> client = mock(AuthenticationClient.class);
         mCallback.onClientFinished(client, true /* success */);
         verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
                 anyBoolean());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index a4048a2..25a700a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -16,24 +16,35 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.app.AppOpsManager.OP_USE_BIOMETRIC;
+import static android.app.AppOpsManager.OP_USE_FINGERPRINT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.common.CommonProps;
-import android.hardware.biometrics.common.SensorStrength;
-import android.hardware.biometrics.fingerprint.FingerprintSensorType;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorLocation;
-import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.testing.TestableContext;
@@ -44,10 +55,13 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -64,6 +78,8 @@
     private static final int ID_VIRTUAL = 6;
     private static final String NAME_DEFAULT = "default";
     private static final String NAME_VIRTUAL = "virtual";
+    private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
+            List.of();
 
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -74,47 +90,80 @@
     public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
 
     @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
     private BiometricContext mBiometricContext;
     @Mock
     private IBiometricService mIBiometricService;
     @Mock
-    private IFingerprint mIFingerprintDefault;
+    private FingerprintProvider mFingerprintDefault;
     @Mock
-    private IFingerprint mIFingerprintVirtual;
+    private FingerprintProvider mFingerprintVirtual;
+    @Mock
+    private IFingerprintServiceReceiver mServiceReceiver;
+    @Mock
+    private IBinder mToken;
 
-    private final SensorProps mSensorPropsDefault = createProps(ID_DEFAULT,
-            SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON);
-    private final SensorProps mSensorPropsVirtual = createProps(ID_VIRTUAL,
-            SensorStrength.STRONG, FingerprintSensorType.UNDER_DISPLAY_OPTICAL);
+    @Captor
+    private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
+
+    private final FingerprintSensorPropertiesInternal mSensorPropsDefault =
+            new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_REAR,
+                    false /* resetLockoutRequiresHardwareAuthToken */);
+    private final FingerprintSensorPropertiesInternal mSensorPropsVirtual =
+            new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_UDFPS_OPTICAL,
+                    false /* resetLockoutRequiresHardwareAuthToken */);
     private FingerprintService mService;
 
     @Before
     public void setup() throws Exception {
-        when(mIFingerprintDefault.getSensorProps()).thenReturn(
-                new SensorProps[]{mSensorPropsDefault});
-        when(mIFingerprintVirtual.getSensorProps()).thenReturn(
-                new SensorProps[]{mSensorPropsVirtual});
+        when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+        when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+        when(mFingerprintDefault.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+        when(mFingerprintVirtual.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
 
-        mContext.getTestablePermissions().setPermission(
-                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+        mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+        for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
+            when(mAppOpsManager.noteOp(eq(permission), anyInt(), any(), any(), any()))
+                    .thenReturn(AppOpsManager.MODE_ALLOWED);
+        }
+
+        for (String permission : List.of(USE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
+            mContext.getTestablePermissions().setPermission(
+                    permission, PackageManager.PERMISSION_GRANTED);
+        }
     }
 
     private void initServiceWith(String... aidlInstances) {
         mService = new FingerprintService(mContext, mBiometricContext,
                 () -> mIBiometricService,
                 () -> aidlInstances,
-                (fqName) -> {
-                    if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
-                    if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
+                (name) -> {
+                    if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
+                    if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
                     return null;
                 });
     }
 
+    private void initServiceWithAndWait(String... aidlInstances) throws Exception {
+        initServiceWith(aidlInstances);
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+        waitForRegistration();
+    }
+
     @Test
     public void registerAuthenticators_defaultOnly() throws Exception {
         initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
 
-        mService.mServiceWrapper.registerAuthenticators(List.of());
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
@@ -126,7 +175,7 @@
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
 
-        mService.mServiceWrapper.registerAuthenticators(List.of());
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -136,7 +185,7 @@
     public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
         initServiceWith(NAME_VIRTUAL);
 
-        mService.mServiceWrapper.registerAuthenticators(List.of());
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -155,13 +204,47 @@
         latch.await(5, TimeUnit.SECONDS);
     }
 
-    private static SensorProps createProps(int id, byte strength, byte type) {
-        final SensorProps props = new SensorProps();
-        props.commonProps = new CommonProps();
-        props.commonProps.sensorId = id;
-        props.commonProps.sensorStrength = strength;
-        props.sensorType = type;
-        props.sensorLocations = new SensorLocation[]{new SensorLocation()};
-        return props;
+    @Test
+    public void authenticateWithDefaultSensorId() throws Exception {
+        initServiceWithAndWait(NAME_DEFAULT, NAME_VIRTUAL);
+
+        final long operationId = 2;
+        mService.mServiceWrapper.authenticate(mToken, operationId, mServiceReceiver,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setSensorId(SENSOR_ID_ANY)
+                        .build());
+
+        final FingerprintAuthenticateOptions options =
+                verifyAuthenticateWithNewRequestId(mFingerprintDefault, operationId);
+        assertThat(options.getSensorId()).isEqualTo(ID_DEFAULT);
+        verifyNoAuthenticate(mFingerprintVirtual);
+    }
+
+
+    private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
+            FingerprintProvider provider, long operationId) {
+        return verifyAuthenticateWithNewRequestId(
+                provider, operationId, true /* shouldSchedule */);
+    }
+
+    private void verifyNoAuthenticate(FingerprintProvider provider) {
+        verifyAuthenticateWithNewRequestId(
+                provider, 0 /* operationId */, false /* shouldSchedule */);
+    }
+
+    private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
+            FingerprintProvider provider, long operationId, boolean shouldSchedule) {
+        verify(provider, shouldSchedule ? times(1) : never())
+                .scheduleAuthenticate(eq(mToken), eq(operationId), anyInt(), any(),
+                        mAuthenticateOptionsCaptor.capture(), anyBoolean(), anyInt(),
+                        anyBoolean());
+        verify(provider, never()).scheduleAuthenticate(eq(mToken), anyLong(),
+                anyInt(), any(), mAuthenticateOptionsCaptor.capture(), anyLong(),
+                anyBoolean(), anyInt(), anyBoolean());
+
+        if (shouldSchedule) {
+            return mAuthenticateOptionsCaptor.getValue();
+        }
+        return null;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 99f7905..f0f975c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -414,11 +415,16 @@
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
+                .setOpPackageName("test-owner")
+                .setUserId(5)
+                .setSensorId(9)
+                .build();
         return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
-                REQUEST_ID, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
-                false /* restricted */, "test-owner", 4 /* cookie */,
+                REQUEST_ID, mClientMonitorCallbackConverter, OP_ID,
+                false /* restricted */, options, 4 /* cookie */,
                 false /* requireConfirmation */,
-                9 /* sensorId */, mBiometricLogger, mBiometricContext,
+                mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */, null /* lockoutCache */,
                 mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 2dbd8f6..e741e44 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -117,8 +118,13 @@
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
         return new FingerprintDetectClient(mContext, () -> aidl, mToken,
-                6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
-                "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+                6 /* requestId */, mClientMonitorCallbackConverter,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setUserId(2)
+                        .setSensorId(1)
+                        .setOpPackageName("a-test")
+                        .build(),
+                mBiometricLogger, mBiometricContext,
                 mUdfpsOverlayController, null, true /* isStrongBiometric */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index f0d8616..5806443 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -164,10 +164,9 @@
     }
 
     protected FingerprintInternalCleanupClient createClient() {
-        final List<Fingerprint> enrollments = new ArrayList<>();
         final Map<Integer, Long> authenticatorIds = new HashMap<>();
         return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */,
-                "the.test.owner", SENSOR_ID, mLogger, mBiometricContext, enrollments,
+                "the.test.owner", SENSOR_ID, mLogger, mBiometricContext,
                 mFingerprintUtils, authenticatorIds) {
             @Override
             protected void onAddUnknownTemplate(int userId,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0cd50f0..9910a80 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,11 +16,11 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 
@@ -35,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -67,7 +68,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.hardware.Sensor;
+import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.IInputManager;
 import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
@@ -144,6 +149,7 @@
     private static final String DEVICE_NAME_3 = "device name 3";
     private static final int DISPLAY_ID_1 = 2;
     private static final int DISPLAY_ID_2 = 3;
+    private static final int NON_EXISTENT_DISPLAY_ID = 42;
     private static final int DEVICE_OWNER_UID_1 = 50;
     private static final int DEVICE_OWNER_UID_2 = 51;
     private static final int UID_1 = 0;
@@ -162,6 +168,8 @@
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
     private static final int VIRTUAL_DEVICE_ID_1 = 42;
     private static final int VIRTUAL_DEVICE_ID_2 = 43;
+    private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG =
+            new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build();
     private static final VirtualDpadConfig DPAD_CONFIG =
             new VirtualDpadConfig.Builder()
                     .setVendorId(VENDOR_ID)
@@ -221,6 +229,8 @@
     @Mock
     private DisplayManagerInternal mDisplayManagerInternalMock;
     @Mock
+    private IDisplayManager mIDisplayManager;
+    @Mock
     private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
     @Mock
     private DevicePolicyManager mDevicePolicyManagerMock;
@@ -237,6 +247,8 @@
     @Mock
     private IVirtualDeviceSoundEffectListener mSoundEffectListener;
     @Mock
+    private IVirtualDisplayCallback mVirtualDisplayCallback;
+    @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     @Mock
     private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -271,9 +283,13 @@
 
     private Intent createRestrictedActivityBlockedIntent(List displayCategories,
             String targetDisplayCategory) {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
+                eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
+                420).setDisplayCategories(displayCategories).build();
+        mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
+                NONBLOCKED_APP_PACKAGE_NAME);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -327,6 +343,7 @@
         mContext = Mockito.spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
+        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManagerMock);
 
@@ -369,15 +386,13 @@
 
     @Test
     public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
-        mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1);
-
-        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
+        assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
                 .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(mDeviceImpl.getDeviceId());
@@ -503,10 +518,9 @@
 
     @Test
     public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).isEmpty();
@@ -514,10 +528,9 @@
 
     @Test
     public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -525,10 +538,10 @@
 
     @Test
     public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -538,11 +551,10 @@
     public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
         VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
                 DEVICE_OWNER_UID_2);
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
 
-        GenericWindowPolicyController gwpc =
-                secondDevice.createWindowPolicyController(new ArrayList<>());
-        secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
@@ -550,16 +562,16 @@
 
     @Test
     public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
                 DEVICE_OWNER_UID_2);
-        GenericWindowPolicyController gwpc1 =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        GenericWindowPolicyController gwpc2 =
-                secondDevice.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1);
-        secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
-        gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
-        gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+
+
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
+        secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+                Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(
@@ -568,8 +580,7 @@
 
     @Test
     public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
 
         mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -609,8 +620,8 @@
                         .setLanguageTag("fr-FR")
                         .build();
 
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
 
         mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
         secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -640,10 +651,9 @@
 
     @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         // This call should not throw any exceptions.
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
     }
 
     @Test
@@ -659,8 +669,8 @@
     @Test
     public void onVirtualDisplayRemovedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
         TestableLooper.get(this).processAllMessages();
@@ -723,8 +733,7 @@
         verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), anyInt(), eq(null));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
@@ -733,12 +742,9 @@
     @Test
     public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
             throws RemoteException {
-        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
-                new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1));
+                () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
@@ -749,13 +755,12 @@
     public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
         final int unknownDisplayId = 999;
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+                () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId));
     }
 
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -764,14 +769,13 @@
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
 
         IBinder wakeLock = wakeLockCaptor.getValue();
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
         verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
     }
 
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -825,7 +829,7 @@
 
     @Test
     public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualTouchscreenConfig positiveConfig =
                 new VirtualTouchscreenConfig.Builder(
                         /* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -863,7 +867,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualNavigationTouchpadConfig positiveConfig =
                 new VirtualNavigationTouchpadConfig.Builder(
                         /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -888,7 +892,7 @@
 
     @Test
     public void createVirtualDpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
@@ -897,7 +901,7 @@
 
     @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
@@ -906,7 +910,7 @@
 
     @Test
     public void createVirtualMouse_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
@@ -915,7 +919,7 @@
 
     @Test
     public void createVirtualTouchscreen_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
@@ -924,7 +928,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
@@ -934,7 +938,7 @@
 
     @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.onAudioSessionStarting(
@@ -951,7 +955,7 @@
 
     @Test
     public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
         assertWithMessage("Virtual dpad should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -961,7 +965,7 @@
 
     @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -971,7 +975,7 @@
 
     @Test
     public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.getInputDeviceDescriptors())
@@ -992,7 +996,7 @@
                         .setAssociatedDisplayId(DISPLAY_ID_1)
                         .build();
 
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.getInputDeviceDescriptors())
@@ -1005,7 +1009,7 @@
 
     @Test
     public void virtualDeviceWithoutKeyboard_noLocaleUpdate() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         // no preceding call to createVirtualKeyboard()
         assertThat(mDeviceImpl.getDeviceLocaleList()).isNull();
@@ -1013,7 +1017,7 @@
 
     @Test
     public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
         assertWithMessage("Virtual mouse should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1023,7 +1027,7 @@
 
     @Test
     public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
         assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1033,7 +1037,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
         assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
                 .that(
@@ -1055,8 +1059,7 @@
 
     @Test
     public void onAudioSessionStarting_hasVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
@@ -1065,8 +1068,7 @@
 
     @Test
     public void onAudioSessionEnded_noVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.onAudioSessionEnded();
@@ -1076,8 +1078,7 @@
 
     @Test
     public void close_cleanVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.close();
@@ -1321,9 +1322,9 @@
 
     @Test
     public void setShowPointerIcon_setsValueForAllDisplays() {
-        mDeviceImpl.mVirtualDisplayIds.add(1);
-        mDeviceImpl.mVirtualDisplayIds.add(2);
-        mDeviceImpl.mVirtualDisplayIds.add(3);
+        addVirtualDisplay(mDeviceImpl, 1);
+        addVirtualDisplay(mDeviceImpl, 2);
+        addVirtualDisplay(mDeviceImpl, 3);
         VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
                 .setAssociatedDisplayId(1)
                 .setInputDeviceName(DEVICE_NAME_1)
@@ -1346,7 +1347,9 @@
         mDeviceImpl.createVirtualMouse(config1, BINDER);
         mDeviceImpl.createVirtualMouse(config2, BINDER);
         mDeviceImpl.createVirtualMouse(config3, BINDER);
+        clearInvocations(mInputManagerInternalMock);
         mDeviceImpl.setShowPointerIcon(false);
+
         verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
         verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
         mDeviceImpl.setShowPointerIcon(true);
@@ -1355,9 +1358,8 @@
 
     @Test
     public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1376,9 +1378,8 @@
 
     @Test
     public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1397,9 +1398,8 @@
 
     @Test
     public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1418,9 +1418,8 @@
 
     @Test
     public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1439,9 +1438,8 @@
 
     @Test
     public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1460,9 +1458,8 @@
 
     @Test
     public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1482,9 +1479,8 @@
     @Test
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
 
         gwpc.onRunningAppsChanged(uids);
@@ -1497,11 +1493,10 @@
     @Test
     public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        gwpc.unregisterRunningAppsChangedListener(mDeviceImpl);
 
         // This call should not throw any exceptions.
         gwpc.onRunningAppsChanged(uids);
@@ -1512,9 +1507,8 @@
     @Test
     public void canActivityBeLaunched_activityCanLaunch() {
         Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1537,9 +1531,8 @@
         doReturn(interceptor).when(interceptor).asBinder();
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1581,9 +1574,8 @@
         doReturn(interceptor).when(interceptor).asBinder();
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1626,8 +1618,7 @@
     }
 
     @Test
-    public void
-            restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+    public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
         Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1654,15 +1645,15 @@
 
     @Test
     public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
         assertThat(displayIds).containsExactly(DISPLAY_ID_1);
     }
 
     @Test
     public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_2);
         ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
         assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
     }
@@ -1677,15 +1668,22 @@
     private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
             VirtualDeviceParams params) {
         VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
-                mInputController, mSensorController, mCameraAccessController,
-                /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
+                mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
+                mInputController, mSensorController, mCameraAccessController
+                /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
                 mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
-                mRunningAppsChangedCallback, params);
+                mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
         mVdms.addVirtualDevice(virtualDeviceImpl);
         return virtualDeviceImpl;
     }
 
+    private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
+                eq(virtualDevice), any(), any())).thenReturn(displayId);
+        virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
+                NONBLOCKED_APP_PACKAGE_NAME);
+    }
+
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
     static final class DropShellPermissionsTemporarily implements AutoCloseable {
         DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index bb28a36..a2e204d 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 23d7082..be13753 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.job;
 
+import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
+import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -525,7 +528,9 @@
             final boolean ui = random.nextBoolean();
             final boolean ej = !ui && random.nextBoolean();
             JobStatus job = createJobStatus("testPendingJobSorting_Random",
-                    createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(250));
+                    createJobInfo(i).setExpedited(ej).setUserInitiated(ui)
+                            .setRequiredNetworkType(ui ? NETWORK_TYPE_ANY : NETWORK_TYPE_NONE),
+                    random.nextInt(250));
             job.enqueueTime = random.nextInt(1_000_000);
             jobQueue.add(job);
         }
@@ -562,7 +567,9 @@
                 final boolean ui = random.nextBoolean();
                 final boolean ej = !ui && random.nextBoolean();
                 JobStatus job = createJobStatus("testPendingJobSortingTransitivity",
-                        createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(50));
+                        createJobInfo(i).setExpedited(ej).setUserInitiated(ui)
+                                .setRequiredNetworkType(ui ? NETWORK_TYPE_ANY : NETWORK_TYPE_NONE),
+                        random.nextInt(50));
                 job.enqueueTime = random.nextInt(1_000_000);
                 job.overrideState = random.nextInt(4);
                 jobQueue.add(job);
@@ -586,7 +593,8 @@
                 final boolean ui = random.nextFloat() < .02;
                 final boolean ej = !ui && random.nextFloat() < .03;
                 JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated",
-                        createJobInfo(i).setExpedited(ej).setUserInitiated(ui),
+                        createJobInfo(i).setExpedited(ej).setUserInitiated(ui)
+                                .setRequiredNetworkType(ui ? NETWORK_TYPE_ANY : NETWORK_TYPE_NONE),
                         random.nextInt(20));
                 job.enqueueTime = random.nextInt(250);
                 job.overrideState = random.nextFloat() < .01
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 9686c38..1c33d0d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -165,10 +165,11 @@
     }
 
     @Override
-    protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
+    protected void tieProfileLockToParent(int profileUserId, int parentUserId,
+            LockscreenCredential password) {
         Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(password, 0);
-        mStorage.writeChildProfileLock(userId, parcel.marshall());
+        mStorage.writeChildProfileLock(profileUserId, parcel.marshall());
         parcel.recycle();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index b20c63c..0a718e3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -118,10 +118,12 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -4005,6 +4007,156 @@
         // TODO Check all other fields
     }
 
+    public void testSaveCorruptAndLoadUser() throws Exception {
+        // First, create some shortcuts and save.
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
+            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                    getTestContext().getResources(), R.drawable.icon2));
+
+            final ShortcutInfo si1 = makeShortcut(
+                    "s1",
+                    "title1-1",
+                    makeComponent(ShortcutActivity.class),
+                    icon1,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                            "key1", "val1", "nest", makeBundle("key", 123)),
+                    /* weight */ 10);
+
+            final ShortcutInfo si2 = makeShortcut(
+                    "s2",
+                    "title1-2",
+                    /* activity */ null,
+                    icon2,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                    /* weight */ 12);
+
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+            assertEquals(2, mManager.getRemainingCallCount());
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64);
+            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                    getTestContext().getResources(), R.drawable.icon2));
+
+            final ShortcutInfo si1 = makeShortcut(
+                    "s1",
+                    "title2-1",
+                    makeComponent(ShortcutActivity.class),
+                    icon1,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                            "key1", "val1", "nest", makeBundle("key", 123)),
+                    /* weight */ 10);
+
+            final ShortcutInfo si2 = makeShortcut(
+                    "s2",
+                    "title2-2",
+                    /* activity */ null,
+                    icon2,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                    /* weight */ 12);
+
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+            assertEquals(2, mManager.getRemainingCallCount());
+        });
+
+        mRunningUsers.put(USER_10, true);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
+            final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                    getTestContext().getResources(), R.drawable.icon2));
+
+            final ShortcutInfo si1 = makeShortcut(
+                    "s1",
+                    "title10-1-1",
+                    makeComponent(ShortcutActivity.class),
+                    icon1,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                            "key1", "val1", "nest", makeBundle("key", 123)),
+                    /* weight */ 10);
+
+            final ShortcutInfo si2 = makeShortcut(
+                    "s2",
+                    "title10-1-2",
+                    /* activity */ null,
+                    icon2,
+                    makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+                    /* weight */ 12);
+
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+            assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+            assertEquals(2, mManager.getRemainingCallCount());
+        });
+
+        // Save and corrupt the primary files.
+        mService.saveDirtyInfo();
+        try (Writer os = new FileWriter(mService.getUserFile(UserHandle.USER_SYSTEM))) {
+            os.write("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<user locales=\"en\" last-app-scan-time2=\"14400000");
+        }
+        try (Writer os = new FileWriter(mService.getUserFile(USER_10))) {
+            os.write("<?xml version='1.0' encoding='utf");
+        }
+
+        // Restore.
+        initService();
+
+        // Before the load, the map should be empty.
+        assertEquals(0, mService.getShortcutsForTest().size());
+
+        // this will pre-load the per-user info.
+        mService.handleUnlockUser(UserHandle.USER_SYSTEM);
+
+        // Now it's loaded.
+        assertEquals(1, mService.getShortcutsForTest().size());
+
+        runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+                    mManager.getDynamicShortcuts()))), "s1", "s2");
+            assertEquals(2, mManager.getRemainingCallCount());
+
+            assertEquals("title1-1", getCallerShortcut("s1").getTitle());
+            assertEquals("title1-2", getCallerShortcut("s2").getTitle());
+        });
+        runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+                    mManager.getDynamicShortcuts()))), "s1", "s2");
+            assertEquals(2, mManager.getRemainingCallCount());
+
+            assertEquals("title2-1", getCallerShortcut("s1").getTitle());
+            assertEquals("title2-2", getCallerShortcut("s2").getTitle());
+        });
+
+        // Start another user
+        mService.handleUnlockUser(USER_10);
+
+        // Now the size is 2.
+        assertEquals(2, mService.getShortcutsForTest().size());
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+                    mManager.getDynamicShortcuts()))), "s1", "s2");
+            assertEquals(2, mManager.getRemainingCallCount());
+
+            assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
+            assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
+        });
+
+        // Try stopping the user
+        mService.handleStopUser(USER_10);
+
+        // Now it's unloaded.
+        assertEquals(1, mService.getShortcutsForTest().size());
+
+        // TODO Check all other fields
+    }
+
     public void testCleanupPackage() {
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index f803355..d78ab867 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -231,6 +231,10 @@
         setExternalStatsSyncLocked(mExternalStatsSync);
     }
 
+    @Override
+    public void writeSyncLocked() {
+    }
+
     public static class DummyExternalStatsSync implements ExternalStatsSync {
         public int flags = 0;
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 5a0867f..daa6823 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -386,16 +386,16 @@
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         assertThrows(SecurityException.class,
-                () -> mTimeDetectorService.clearNetworkTime());
+                () -> mTimeDetectorService.clearLatestNetworkTime());
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
     }
 
     @Test
-    public void testClearNetworkTime() throws Exception {
+    public void testClearLatestNetworkSuggestion() throws Exception {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        mTimeDetectorService.clearNetworkTime();
+        mTimeDetectorService.clearLatestNetworkTime();
 
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
@@ -403,53 +403,48 @@
     }
 
     @Test
-    public void testLatestNetworkTime() {
-        NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
-                1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
-        when(mMockNtpTrustedTime.getCachedTimeResult())
-                .thenReturn(latestNetworkTime);
-        UnixEpochTime expected = new UnixEpochTime(
-                latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
-        assertEquals(expected, mTimeDetectorService.latestNetworkTime());
-    }
-
-    @Test
-    public void testLatestNetworkTime_noTimeAvailable() {
-        when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
-        assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
-    }
-
-    @Test
     public void testGetLatestNetworkSuggestion() {
-        if (TimeDetectorNetworkTimeHelper.isInUse()) {
-            NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion();
-            mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime);
+        NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+        mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
 
-            assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion());
+        assertEquals(latestNetworkSuggestion, mTimeDetectorService.getLatestNetworkSuggestion());
+    }
+
+    @Test
+    public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+        mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
+
+        assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+    }
+
+    @Test
+    public void testLatestNetworkTime() {
+        if (TimeDetectorNetworkTimeHelper.isInUse()) {
+            NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+            mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
+
+            assertEquals(latestNetworkSuggestion.getUnixEpochTime(),
+                    mTimeDetectorService.latestNetworkTime());
         } else {
             NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
                     1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
             when(mMockNtpTrustedTime.getCachedTimeResult())
                     .thenReturn(latestNetworkTime);
-            UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+            UnixEpochTime expected = new UnixEpochTime(
                     latestNetworkTime.getElapsedRealtimeMillis(),
                     latestNetworkTime.getTimeMillis());
-            NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
-                    expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
-            assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+            assertEquals(expected, mTimeDetectorService.latestNetworkTime());
         }
     }
 
     @Test
-    public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+    public void testLatestNetworkTime_noTimeAvailable() {
         if (TimeDetectorNetworkTimeHelper.isInUse()) {
             mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
-
-            assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
         } else {
             when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
-            assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
         }
+        assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
     }
 
     @Test
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index b921838..4c0361d 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -263,7 +263,7 @@
                 + instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " "
                 + packageName + " 0");
         waitUntil("Failed to get shortcut access",
-                () -> hasShortcutAccess(instrumentation, packageName), 20);
+                () -> hasShortcutAccess(instrumentation, packageName), 60);
     }
 
     public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
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 3ecbbfeed..8f0a5e6 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -141,9 +141,9 @@
      */
     @Test
     public void testMetaN() throws RemoteException {
-        mPhoneWindowManager.overrideExpandNotificationsPanel();
+        mPhoneWindowManager.overrideTogglePanel();
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0);
-        mPhoneWindowManager.assertExpandNotification();
+        mPhoneWindowManager.assertTogglePanel();
     }
 
     /**
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 6da9d0c..b693974 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -67,6 +67,7 @@
 import android.os.Vibrator;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
+import android.util.FeatureFlagUtils;
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillManagerInternal;
@@ -76,6 +77,7 @@
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -114,6 +116,7 @@
     @Mock private Vibrator mVibrator;
     @Mock private PowerManager mPowerManager;
     @Mock private WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncsImpl;
+    @Mock private InputMethodManagerInternal mInputMethodManagerInternal;
     @Mock private AudioManagerInternal mAudioManagerInternal;
     @Mock private SearchManager mSearchManager;
 
@@ -184,6 +187,8 @@
                 () -> LocalServices.getService(eq(GestureLauncherService.class)));
         doReturn(null).when(() -> LocalServices.getService(eq(VrManagerInternal.class)));
         doReturn(null).when(() -> LocalServices.getService(eq(AutofillManagerInternal.class)));
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
+        LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
 
         doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
@@ -242,6 +247,7 @@
 
     void tearDown() {
         mHandlerThread.quitSafely();
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         mMockitoSession.finishMocking();
     }
 
@@ -322,12 +328,12 @@
         doReturn(true).when(mTelecomManager).endCall();
     }
 
-    void overrideExpandNotificationsPanel() {
+    void overrideTogglePanel() {
         // Can't directly mock on IStatusbarService, use spyOn and override the specific api.
         mPhoneWindowManager.getStatusBarService();
         spyOn(mPhoneWindowManager.mStatusBarService);
         try {
-            doNothing().when(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+            doNothing().when(mPhoneWindowManager.mStatusBarService).togglePanel();
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -417,7 +423,13 @@
 
     void assertSwitchKeyboardLayout(int direction) {
         waitForIdle();
-        verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
+            verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
+            verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
+        } else {
+            verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
+            verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt());
+        }
     }
 
     void assertTakeBugreport() {
@@ -428,9 +440,9 @@
         Assert.assertTrue(intentCaptor.getValue().getAction() == Intent.ACTION_BUG_REPORT);
     }
 
-    void assertExpandNotification() throws RemoteException {
+    void assertTogglePanel() throws RemoteException {
         waitForIdle();
-        verify(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+        verify(mPhoneWindowManager.mStatusBarService).togglePanel();
     }
 
     void assertToggleShortcutsMenu() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 4359234..025e1dc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -30,7 +30,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -41,7 +41,6 @@
 import android.media.AudioManagerInternal;
 import android.media.permission.Identity;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -89,8 +88,7 @@
     static final boolean DEBUG = false;
 
     /**
-     * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above,
-     * implementors of the HotwordDetectionService must not augment the phrase IDs which are
+     * Implementors of the HotwordDetectionService must not augment the phrase IDs which are
      * supplied via HotwordDetectionService
      * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
      *
@@ -104,7 +102,7 @@
      * </p>
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Disabled
     public static final long ENFORCE_HOTWORD_PHRASE_ID = 215066299L;
 
     private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 6b2bea0..97538c1 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -76,7 +76,10 @@
     }
 
     /**
-     * Request Telecom set the call state to active.
+     * Request Telecom set the call state to active. This method should be called when either an
+     * outgoing call is ready to go active or a held call is ready to go active again. For incoming
+     * calls that are ready to be answered, use
+     * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
      *
      * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
      *                 will be called on.
@@ -106,6 +109,43 @@
     }
 
     /**
+     * Request Telecom answer an incoming call.  For outgoing calls and calls that have been placed
+     * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
+     *
+     * @param videoState to report to Telecom. Telecom will store VideoState in the event another
+     *                   service/device requests it in order to continue the call on another screen.
+     * @param executor   The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                   will be called on.
+     * @param callback   that will be completed on the Telecom side that details success or failure
+     *                   of the requested operation.
+     *
+     *                   {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                   switched the call state to active
+     *
+     *                   {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                   the call state to active.  A {@link CallException} will be passed
+     *                   that details why the operation failed.
+     */
+    public void answer(@android.telecom.CallAttributes.CallType int videoState,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        validateVideoState(videoState);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.answer(videoState, mCallId,
+                        new CallControlResultReceiver("answer", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
      * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
      * but can be extended to setting a meeting to inactive.
      *
@@ -343,4 +383,13 @@
         }
     }
 
+    /** @hide */
+    private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
+        if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
+            throw new IllegalArgumentException(TextUtils.formatSimple(
+                    "The VideoState argument passed in, %d , is not a valid VideoState. The "
+                            + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
+                            + "CallAttributes.VIDEO_CALL", videoState));
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b6def1a..94c737d 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -1227,6 +1227,12 @@
         if (hasCapabilities(CAPABILITY_VOICE_CALLING_AVAILABLE)) {
             sb.append("Voice ");
         }
+        if (hasCapabilities(CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)) {
+            sb.append("TransactOps ");
+        }
+        if (hasCapabilities(CAPABILITY_SUPPORTS_CALL_STREAMING)) {
+            sb.append("Stream ");
+        }
         return sb.toString();
     }
 
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 3e651e9..5e2c923 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -27,6 +27,7 @@
  */
 oneway interface ICallControl {
     void setActive(String callId, in ResultReceiver callback);
+    void answer(int videoState, String callId, in ResultReceiver callback);
     void setInactive(String callId, in ResultReceiver callback);
     void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
     void startCallStreaming(String callId, in ResultReceiver callback);
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index 98ed1fa..ecd7039 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -584,6 +584,7 @@
      * Gets the call ID of the session.
      *
      * @return the call ID
+     * If null is returned for getCallId, then that means that the call ID has not been set yet.
      */
     public String getCallId() {
         if (mClosed) {
@@ -1779,7 +1780,7 @@
         sb.append("[ImsCallSession objId:");
         sb.append(System.identityHashCode(this));
         sb.append(" callId:");
-        sb.append(getCallId());
+        sb.append(mCallId != null ? mCallId : "[UNINITIALIZED]");
         sb.append("]");
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index f8cf81c..c352266 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -615,9 +615,7 @@
 
     /**
      * The default state indicating that datagram transfer is idle.
-     * This should be sent immediately after either
-     * {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS} or
-     * {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED}.
+     * This should be sent if there are no message transfer activity happening.
      */
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0;
     /**
@@ -625,25 +623,41 @@
      */
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1;
     /**
+     * An end state indicating that datagram sending completed successfully.
+     * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
+     * will be sent if no more messages are pending.
+     */
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2;
+    /**
+     * An end state indicating that datagram sending completed with a failure.
+     * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
+     * must be sent before reporting any additional datagram transfer state changes. All pending
+     * messages will be reported as failed, to the corresponding applications.
+     */
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3;
+    /**
      * A transition state indicating that a datagram is being received.
      */
-    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 2;
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4;
     /**
-     * A transition state indicating that datagram transfer is being retried.
-     */
-    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RETRYING = 3;
-    /**
-     * An end state indicating that datagram transfer completed successfully.
+     * An end state indicating that datagram receiving completed successfully.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
-     * must be sent before reporting any additional datagram transfer state changes.
+     * will be sent if no more messages are pending.
      */
-    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS = 4;
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5;
     /**
-     * An end state indicating that datagram transfer completed with a failure.
+     * An end state indicating that datagram receive operation found that there are no
+     * messages to be retrieved from the satellite.
      * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
-     * must be sent before reporting any additional datagram transfer state changes.
+     * will be sent if no more messages are pending.
      */
-    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED = 5;
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6;
+    /**
+     * An end state indicating that datagram receive completed with a failure.
+     * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE}
+     * will be sent if no more messages are pending.
+     */
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7;
     /**
      * The datagram transfer state is unknown. This generic datagram transfer state should be used
      * only when the datagram transfer state cannot be mapped to other specific datagram transfer
@@ -655,10 +669,12 @@
     @IntDef(prefix = {"SATELLITE_DATAGRAM_TRANSFER_STATE_"}, value = {
             SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
             SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+            SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
+            SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
             SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
-            SATELLITE_DATAGRAM_TRANSFER_STATE_RETRYING,
-            SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS,
-            SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED,
+            SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
+            SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE,
+            SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
             SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -733,9 +749,6 @@
      * All other results indicate that this operation failed.
      * Once satellite position updates begin, datagram transfer state updates will be sent
      * through {@link SatellitePositionUpdateCallback}.
-     * Modem should report any changes in datagram transfer state and indicate success or failure
-     * by reporting {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS} or
-     * {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED}.
      *
      * @param executor The executor on which the callback and error code listener will be called.
      * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index c5169e5..d1a68d4 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -235,7 +235,6 @@
     method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
     method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
     method @Deprecated public String getNameForUid(int);
-    method @Deprecated public android.content.pm.PackageInfo getPackageArchiveInfo(String, int);
     method @Deprecated public int[] getPackageGids(String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index d5d01d3..a19510b 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -192,9 +192,9 @@
                 mService = ISharedConnectivityService.Stub.asInterface(service);
                 if (!mCallbackProxyCache.isEmpty()) {
                     synchronized (mCallbackProxyCache) {
-                        mCallbackProxyCache.keySet().forEach(callback -> {
-                            registerCallbackInternal(callback, mCallbackProxyCache.get(callback));
-                        });
+                        mCallbackProxyCache.keySet().forEach(callback ->
+                                registerCallbackInternal(
+                                        callback, mCallbackProxyCache.get(callback)));
                         mCallbackProxyCache.clear();
                     }
                 }
@@ -258,7 +258,9 @@
     }
 
     /**
-     * Registers a callback for receiving updates to the list of Tether Networks and Known Networks.
+     * Registers a callback for receiving updates to the list of Tether Networks, Known Networks,
+     * shared connectivity settings state, tether network connection status and known network
+     * connection status.
      * The {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} will be called if the
      * registration failed.
      *
@@ -418,4 +420,104 @@
         }
         return true;
     }
+
+    /**
+     * Gets the list of tether networks the user can select to connect to.
+     *
+     * @return Returns a {@link List} of {@link TetherNetwork} objects, empty list on failure.
+     */
+    @NonNull
+    public List<TetherNetwork> getTetherNetworks() {
+        if (mService == null) {
+            return List.of();
+        }
+
+        try {
+            return mService.getTetherNetworks();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in getTetherNetworks", e);
+        }
+        return List.of();
+    }
+
+    /**
+     * Gets the list of known networks the user can select to connect to.
+     *
+     * @return Returns a {@link List} of {@link KnownNetwork} objects, empty list on failure.
+     */
+    @NonNull
+    public List<KnownNetwork> getKnownNetworks() {
+        if (mService == null) {
+            return List.of();
+        }
+
+        try {
+            return mService.getKnownNetworks();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in getKnownNetworks", e);
+        }
+        return List.of();
+    }
+
+    /**
+     * Gets the shared connectivity settings state.
+     *
+     * @return Returns a {@link SharedConnectivitySettingsState} object with the state, null on
+     * failure.
+     */
+    @Nullable
+    public SharedConnectivitySettingsState getSettingsState() {
+        if (mService == null) {
+            return null;
+        }
+
+        try {
+            return mService.getSettingsState();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in getSettingsState", e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the connection status of the tether network the user selected to connect to.
+     *
+     * @return Returns a {@link TetherNetworkConnectionStatus} object with the connection status,
+     * null on failure. If no connection is active the status will be
+     * {@link TetherNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}.
+     */
+    @Nullable
+    public TetherNetworkConnectionStatus getTetherNetworkConnectionStatus() {
+        if (mService == null) {
+            return null;
+        }
+
+        try {
+            return mService.getTetherNetworkConnectionStatus();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in getTetherNetworkConnectionStatus", e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the connection status of the known network the user selected to connect to.
+     *
+     * @return Returns a {@link KnownNetworkConnectionStatus} object with the connection status,
+     * null on failure. If no connection is active the status will be
+     * {@link KnownNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}.
+     */
+    @Nullable
+    public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() {
+        if (mService == null) {
+            return null;
+        }
+
+        try {
+            return mService.getKnownNetworkConnectionStatus();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in getKnownNetworkConnectionStatus", e);
+        }
+        return null;
+    }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
index 52da596..9f33e99 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
@@ -18,6 +18,9 @@
 
 import android.net.wifi.sharedconnectivity.app.KnownNetwork;
 import android.net.wifi.sharedconnectivity.app.TetherNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus;
 import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback;
 
 /*
@@ -30,4 +33,9 @@
     void disconnectTetherNetwork(in TetherNetwork network);
     void connectKnownNetwork(in KnownNetwork network);
     void forgetKnownNetwork(in KnownNetwork network);
+    List<TetherNetwork> getTetherNetworks();
+    List<KnownNetwork> getKnownNetworks();
+    SharedConnectivitySettingsState getSettingsState();
+    TetherNetworkConnectionStatus getTetherNetworkConnectionStatus();
+    KnownNetworkConnectionStatus getKnownNetworkConnectionStatus();
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index ff7246f..d2cea61 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -33,16 +33,15 @@
 import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
 import android.net.wifi.sharedconnectivity.app.TetherNetwork;
 import android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 
 /**
@@ -61,30 +60,21 @@
     private static final boolean DEBUG = true;
 
     private Handler mHandler;
-    private final List<ISharedConnectivityCallback> mCallbacks = new ArrayList<>();
-    // Used to find DeathRecipient when unregistering a callback to call unlinkToDeath.
-    private final Map<ISharedConnectivityCallback, DeathRecipient> mDeathRecipientMap =
-            new HashMap<>();
-
+    private final RemoteCallbackList<ISharedConnectivityCallback> mRemoteCallbackList =
+            new RemoteCallbackList<>();
     private List<TetherNetwork> mTetherNetworks = Collections.emptyList();
     private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
-    private SharedConnectivitySettingsState mSettingsState;
-    private TetherNetworkConnectionStatus mTetherNetworkConnectionStatus;
-    private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus;
-
-    private final class DeathRecipient implements IBinder.DeathRecipient {
-        ISharedConnectivityCallback mCallback;
-
-        DeathRecipient(ISharedConnectivityCallback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void binderDied() {
-            mCallbacks.remove(mCallback);
-            mDeathRecipientMap.remove(mCallback);
-        }
-    }
+    private SharedConnectivitySettingsState mSettingsState =
+            new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(false)
+                    .setExtras(Bundle.EMPTY).build();
+    private TetherNetworkConnectionStatus mTetherNetworkConnectionStatus =
+            new TetherNetworkConnectionStatus.Builder()
+                    .setStatus(TetherNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
+                    .setExtras(Bundle.EMPTY).build();
+    private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus =
+            new KnownNetworkConnectionStatus.Builder()
+                    .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
+                    .setExtras(Bundle.EMPTY).build();
 
     @Override
     @Nullable
@@ -128,12 +118,49 @@
                 mHandler.post(() -> onForgetKnownNetwork(network));
             }
 
+            @Override
+            public List<TetherNetwork> getTetherNetworks() {
+                checkPermissions();
+                return mTetherNetworks;
+            }
+
+            @Override
+            public List<KnownNetwork> getKnownNetworks() {
+                checkPermissions();
+                return mKnownNetworks;
+            }
+
+            @Override
+            public SharedConnectivitySettingsState getSettingsState() {
+                checkPermissions();
+                return mSettingsState;
+            }
+
+            @Override
+            public TetherNetworkConnectionStatus getTetherNetworkConnectionStatus() {
+                checkPermissions();
+                return mTetherNetworkConnectionStatus;
+            }
+
+            @Override
+            public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() {
+                checkPermissions();
+                return mKnownNetworkConnectionStatus;
+            }
+
             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
+            /**
+             * checkPermissions is using checkCallingOrSelfPermission to support CTS testing of this
+             * service. This does allow a process to bind to itself if it holds the proper
+             * permission. We do not consider this to be an issue given that the process can already
+             * access the service data since they are in the same process.
+             */
             private void checkPermissions() {
-                if (checkCallingPermission(NETWORK_SETTINGS) != PackageManager.PERMISSION_GRANTED
-                        && checkCallingPermission(NETWORK_SETUP_WIZARD)
-                                != PackageManager.PERMISSION_GRANTED) {
+                if (checkCallingOrSelfPermission(NETWORK_SETTINGS)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkCallingOrSelfPermission(NETWORK_SETUP_WIZARD)
+                        != PackageManager.PERMISSION_GRANTED) {
                     throw new SecurityException("Calling process must have NETWORK_SETTINGS or"
                             + " NETWORK_SETUP_WIZARD permission");
                 }
@@ -148,85 +175,13 @@
     public void onBind() {}
 
     private void onRegisterCallback(ISharedConnectivityCallback callback) {
-        // Listener gets triggered on first register using cashed data
-        if (!notifyTetherNetworkUpdate(callback) || !notifyKnownNetworkUpdate(callback)
-                || !notifySettingsStateUpdate(callback)
-                || !notifyTetherNetworkConnectionStatusChanged(callback)
-                || !notifyKnownNetworkConnectionStatusChanged(callback)) {
-            if (DEBUG) Log.w(TAG, "Failed to notify client");
-            return;
-        }
-
-        DeathRecipient deathRecipient = new DeathRecipient(callback);
-        try {
-            callback.asBinder().linkToDeath(deathRecipient, 0);
-            mCallbacks.add(callback);
-            mDeathRecipientMap.put(callback, deathRecipient);
-        } catch (RemoteException e) {
-            if (DEBUG) Log.w(TAG, "Exception in registerCallback", e);
-        }
+        mRemoteCallbackList.register(callback);
     }
 
     private void onUnregisterCallback(ISharedConnectivityCallback callback) {
-        DeathRecipient deathRecipient = mDeathRecipientMap.get(callback);
-        if (deathRecipient != null) {
-            callback.asBinder().unlinkToDeath(deathRecipient, 0);
-            mDeathRecipientMap.remove(callback);
-        }
-        mCallbacks.remove(callback);
+        mRemoteCallbackList.unregister(callback);
     }
 
-    private boolean notifyTetherNetworkUpdate(ISharedConnectivityCallback callback) {
-        try {
-            callback.onTetherNetworksUpdated(mTetherNetworks);
-        } catch (RemoteException e) {
-            if (DEBUG) Log.w(TAG, "Exception in notifyTetherNetworkUpdate", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean notifyKnownNetworkUpdate(ISharedConnectivityCallback callback) {
-        try {
-            callback.onKnownNetworksUpdated(mKnownNetworks);
-        } catch (RemoteException e) {
-            if (DEBUG) Log.w(TAG, "Exception in notifyKnownNetworkUpdate", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean notifySettingsStateUpdate(ISharedConnectivityCallback callback) {
-        try {
-            callback.onSharedConnectivitySettingsChanged(mSettingsState);
-        } catch (RemoteException e) {
-            if (DEBUG) Log.w(TAG, "Exception in notifySettingsStateUpdate", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean notifyTetherNetworkConnectionStatusChanged(
-            ISharedConnectivityCallback callback) {
-        try {
-            callback.onTetherNetworkConnectionStatusChanged(mTetherNetworkConnectionStatus);
-        } catch (RemoteException e) {
-            if (DEBUG) Log.w(TAG, "Exception in notifyTetherNetworkConnectionStatusChanged", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean notifyKnownNetworkConnectionStatusChanged(
-            ISharedConnectivityCallback callback) {
-        try {
-            callback.onKnownNetworkConnectionStatusChanged(mKnownNetworkConnectionStatus);
-        } catch (RemoteException e) {
-            if (DEBUG) Log.w(TAG, "Exception in notifyKnownNetworkConnectionStatusChanged", e);
-            return false;
-        }
-        return true;
-    }
     /**
      * Implementing application should call this method to provide an up-to-date list of Tether
      * Networks to be displayed to the user.
@@ -239,9 +194,15 @@
     public final void setTetherNetworks(@NonNull List<TetherNetwork> networks) {
         mTetherNetworks = networks;
 
-        for (ISharedConnectivityCallback callback:mCallbacks) {
-            notifyTetherNetworkUpdate(callback);
+        int count = mRemoteCallbackList.beginBroadcast();
+        for (int i = 0; i < count; i++) {
+            try {
+                mRemoteCallbackList.getBroadcastItem(i).onTetherNetworksUpdated(mTetherNetworks);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.w(TAG, "Exception in setTetherNetworks", e);
+            }
         }
+        mRemoteCallbackList.finishBroadcast();
     }
 
     /**
@@ -256,9 +217,15 @@
     public final void setKnownNetworks(@NonNull List<KnownNetwork> networks) {
         mKnownNetworks = networks;
 
-        for (ISharedConnectivityCallback callback:mCallbacks) {
-            notifyKnownNetworkUpdate(callback);
+        int count = mRemoteCallbackList.beginBroadcast();
+        for (int i = 0; i < count; i++) {
+            try {
+                mRemoteCallbackList.getBroadcastItem(i).onKnownNetworksUpdated(mKnownNetworks);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.w(TAG, "Exception in setKnownNetworks", e);
+            }
         }
+        mRemoteCallbackList.finishBroadcast();
     }
 
     /**
@@ -274,9 +241,16 @@
     public final void setSettingsState(@NonNull SharedConnectivitySettingsState settingsState) {
         mSettingsState = settingsState;
 
-        for (ISharedConnectivityCallback callback:mCallbacks) {
-            notifySettingsStateUpdate(callback);
+        int count = mRemoteCallbackList.beginBroadcast();
+        for (int i = 0; i < count; i++) {
+            try {
+                mRemoteCallbackList.getBroadcastItem(i).onSharedConnectivitySettingsChanged(
+                        mSettingsState);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.w(TAG, "Exception in setSettingsState", e);
+            }
         }
+        mRemoteCallbackList.finishBroadcast();
     }
 
     /**
@@ -289,9 +263,18 @@
     public final void updateTetherNetworkConnectionStatus(
             @NonNull TetherNetworkConnectionStatus status) {
         mTetherNetworkConnectionStatus = status;
-        for (ISharedConnectivityCallback callback:mCallbacks) {
-            notifyTetherNetworkConnectionStatusChanged(callback);
+
+        int count = mRemoteCallbackList.beginBroadcast();
+        for (int i = 0; i < count; i++) {
+            try {
+                mRemoteCallbackList
+                        .getBroadcastItem(i).onTetherNetworkConnectionStatusChanged(
+                                mTetherNetworkConnectionStatus);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.w(TAG, "Exception in updateTetherNetworkConnectionStatus", e);
+            }
         }
+        mRemoteCallbackList.finishBroadcast();
     }
 
     /**
@@ -305,9 +288,17 @@
             @NonNull KnownNetworkConnectionStatus status) {
         mKnownNetworkConnectionStatus = status;
 
-        for (ISharedConnectivityCallback callback:mCallbacks) {
-            notifyKnownNetworkConnectionStatusChanged(callback);
+        int count = mRemoteCallbackList.beginBroadcast();
+        for (int i = 0; i < count; i++) {
+            try {
+                mRemoteCallbackList
+                        .getBroadcastItem(i).onKnownNetworkConnectionStatusChanged(
+                                mKnownNetworkConnectionStatus);
+            } catch (RemoteException e) {
+                if (DEBUG) Log.w(TAG, "Exception in updateKnownNetworkConnectionStatus", e);
+            }
         }
+        mRemoteCallbackList.finishBroadcast();
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 439d456..cdb438f 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -22,6 +22,8 @@
 import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
 import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -36,6 +38,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.RemoteException;
 
@@ -46,6 +49,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -95,6 +99,7 @@
     @Test
     public void bindingToService() {
         SharedConnectivityManager.create(mContext);
+
         verify(mContext).bindService(any(), any(), anyInt());
     }
 
@@ -104,6 +109,7 @@
     @Test
     public void resourcesNotDefined() {
         when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException());
+
         assertNull(SharedConnectivityManager.create(mContext));
     }
 
@@ -115,8 +121,10 @@
             throws Exception {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
+
         // Since the binder is embedded in a proxy class, the call to registerCallback is done on
         // the proxy. So instead verifying that the proxy is calling the binder.
         verify(mIBinder).transact(anyInt(), any(Parcel.class), any(Parcel.class), anyInt());
@@ -126,9 +134,11 @@
     public void registerCallback_serviceNotConnected_canUnregisterAndReregister() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.unregisterCallback(mClientCallback);
         manager.registerCallback(mExecutor, mClientCallback);
+
         verify(mClientCallback, never()).onRegisterCallbackFailed(any(Exception.class));
     }
 
@@ -136,7 +146,9 @@
     public void registerCallback_serviceConnected() throws Exception {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.registerCallback(mExecutor, mClientCallback);
+
         verify(mService).registerCallback(any());
         verify(mClientCallback, never()).onRegisterCallbackFailed(any(Exception.class));
     }
@@ -145,8 +157,10 @@
     public void registerCallback_doubleRegistration_shouldFail() throws Exception {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.registerCallback(mExecutor, mClientCallback);
+
         verify(mClientCallback).onRegisterCallbackFailed(any(IllegalStateException.class));
     }
 
@@ -155,7 +169,9 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).registerCallback(any());
+
         manager.registerCallback(mExecutor, mClientCallback);
+
         verify(mClientCallback).onRegisterCallbackFailed(any(RemoteException.class));
     }
 
@@ -166,6 +182,7 @@
     public void unregisterCallback_withoutRegisteringFirst_serviceNotConnected_shouldFail() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         assertFalse(manager.unregisterCallback(mClientCallback));
     }
 
@@ -173,6 +190,7 @@
     public void unregisterCallback_withoutRegisteringFirst_serviceConnected_shouldFail() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         assertFalse(manager.unregisterCallback(mClientCallback));
     }
 
@@ -180,7 +198,9 @@
     public void unregisterCallback() throws Exception {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.registerCallback(mExecutor, mClientCallback);
+
         assertTrue(manager.unregisterCallback(mClientCallback));
         verify(mService).unregisterCallback(any());
     }
@@ -189,8 +209,10 @@
     public void unregisterCallback_doubleUnregistration_serviceConnected_shouldFail() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.unregisterCallback(mClientCallback);
+
         assertFalse(manager.unregisterCallback(mClientCallback));
     }
 
@@ -198,8 +220,10 @@
     public void unregisterCallback_doubleUnregistration_serviceNotConnected_shouldFail() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.unregisterCallback(mClientCallback);
+
         assertFalse(manager.unregisterCallback(mClientCallback));
     }
 
@@ -207,7 +231,9 @@
     public void unregisterCallback_remoteException_shouldFail() throws Exception {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         doThrow(new RemoteException()).when(mService).unregisterCallback(any());
+
         assertFalse(manager.unregisterCallback(mClientCallback));
     }
 
@@ -217,16 +243,20 @@
     @Test
     public void onServiceConnected_registerCallbackBeforeConnection() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
+
         verify(mClientCallback).onServiceConnected();
     }
 
     @Test
     public void onServiceConnected_registerCallbackAfterConnection() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+
         manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
         manager.registerCallback(mExecutor, mClientCallback);
+
         verify(mClientCallback).onServiceConnected();
     }
 
@@ -236,18 +266,22 @@
     @Test
     public void onServiceDisconnected_registerCallbackBeforeConnection() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+
         manager.registerCallback(mExecutor, mClientCallback);
         manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
         manager.getServiceConnection().onServiceDisconnected(COMPONENT_NAME);
+
         verify(mClientCallback).onServiceDisconnected();
     }
 
     @Test
     public void onServiceDisconnected_registerCallbackAfterConnection() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+
         manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder);
         manager.registerCallback(mExecutor, mClientCallback);
         manager.getServiceConnection().onServiceDisconnected(COMPONENT_NAME);
+
         verify(mClientCallback).onServiceDisconnected();
     }
 
@@ -259,6 +293,7 @@
         TetherNetwork network = buildTetherNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         assertFalse(manager.connectTetherNetwork(network));
     }
 
@@ -267,7 +302,9 @@
         TetherNetwork network = buildTetherNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.connectTetherNetwork(network);
+
         verify(mService).connectTetherNetwork(network);
     }
 
@@ -277,6 +314,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).connectTetherNetwork(network);
+
         assertFalse(manager.connectTetherNetwork(network));
     }
 
@@ -288,6 +326,7 @@
         TetherNetwork network = buildTetherNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         assertFalse(manager.disconnectTetherNetwork(network));
     }
 
@@ -296,7 +335,9 @@
         TetherNetwork network = buildTetherNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.disconnectTetherNetwork(network);
+
         verify(mService).disconnectTetherNetwork(network);
     }
 
@@ -306,6 +347,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).disconnectTetherNetwork(any());
+
         assertFalse(manager.disconnectTetherNetwork(network));
     }
 
@@ -317,6 +359,7 @@
         KnownNetwork network = buildKnownNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         assertFalse(manager.connectKnownNetwork(network));
     }
 
@@ -325,7 +368,9 @@
         KnownNetwork network = buildKnownNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.connectKnownNetwork(network);
+
         verify(mService).connectKnownNetwork(network);
     }
 
@@ -335,6 +380,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).connectKnownNetwork(network);
+
         assertFalse(manager.connectKnownNetwork(network));
     }
 
@@ -346,6 +392,7 @@
         KnownNetwork network = buildKnownNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
+
         assertFalse(manager.forgetKnownNetwork(network));
     }
 
@@ -354,7 +401,9 @@
         KnownNetwork network = buildKnownNetwork();
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
+
         manager.forgetKnownNetwork(network);
+
         verify(mService).forgetKnownNetwork(network);
     }
 
@@ -364,9 +413,162 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).forgetKnownNetwork(network);
+
         assertFalse(manager.forgetKnownNetwork(network));
     }
 
+    /**
+     * Verify getters.
+     */
+    @Test
+    public void getTetherNetworks_serviceNotConnected_shouldReturnEmptyList() {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(null);
+
+        assertArrayEquals(List.of().toArray(), manager.getTetherNetworks().toArray());
+    }
+
+    @Test
+    public void getTetherNetworks_remoteException_shouldReturnEmptyList() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).getTetherNetworks();
+
+        assertArrayEquals(List.of().toArray(), manager.getTetherNetworks().toArray());
+    }
+
+    @Test
+    public void getTetherNetworks_shouldReturnNetworksList() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        List<TetherNetwork> networks = List.of(buildTetherNetwork());
+        List<TetherNetwork> expected = List.of(buildTetherNetwork());
+        manager.setService(mService);
+        when(mService.getTetherNetworks()).thenReturn(networks);
+
+        assertArrayEquals(expected.toArray(), manager.getTetherNetworks().toArray());
+    }
+
+    @Test
+    public void getKnownNetworks_serviceNotConnected_shouldReturnEmptyList()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(null);
+
+        assertArrayEquals(List.of().toArray(), manager.getKnownNetworks().toArray());
+    }
+
+    @Test
+    public void getKnownNetworks_remoteException_shouldReturnEmptyList() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).getKnownNetworks();
+
+        assertArrayEquals(List.of().toArray(), manager.getKnownNetworks().toArray());
+    }
+
+    @Test
+    public void getKnownNetworks_shouldReturnNetworksList() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        List<KnownNetwork> networks = List.of(buildKnownNetwork());
+        List<KnownNetwork> expected = List.of(buildKnownNetwork());
+        manager.setService(mService);
+        when(mService.getKnownNetworks()).thenReturn(networks);
+
+        assertArrayEquals(expected.toArray(), manager.getKnownNetworks().toArray());
+    }
+
+    @Test
+    public void getSettingsState_serviceNotConnected_shouldReturnNull() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(null);
+
+        assertNull(manager.getSettingsState());
+    }
+
+    @Test
+    public void getSettingsState_remoteException_shouldReturnNull() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).getSettingsState();
+
+        assertNull(manager.getSettingsState());
+    }
+
+    @Test
+    public void getSettingsState_serviceConnected_shouldReturnState() throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder()
+                .setInstantTetherEnabled(true).setExtras(new Bundle()).build();
+        manager.setService(mService);
+        when(mService.getSettingsState()).thenReturn(state);
+
+        assertEquals(state, manager.getSettingsState());
+    }
+
+    @Test
+    public void getTetherNetworkConnectionStatus_serviceNotConnected_shouldReturnNull()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(null);
+
+        assertNull(manager.getTetherNetworkConnectionStatus());
+    }
+
+    @Test
+    public void getTetherNetworkConnectionStatus_remoteException_shouldReturnNull()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).getTetherNetworkConnectionStatus();
+
+        assertNull(manager.getTetherNetworkConnectionStatus());
+    }
+
+    @Test
+    public void getTetherNetworkConnectionStatus_serviceConnected_shouldReturnStatus()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        TetherNetworkConnectionStatus status = new TetherNetworkConnectionStatus.Builder()
+                .setStatus(TetherNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT)
+                .setExtras(new Bundle()).build();
+        manager.setService(mService);
+        when(mService.getTetherNetworkConnectionStatus()).thenReturn(status);
+
+        assertEquals(status, manager.getTetherNetworkConnectionStatus());
+    }
+
+    @Test
+    public void getKnownNetworkConnectionStatus_serviceNotConnected_shouldReturnNull()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(null);
+
+        assertNull(manager.getKnownNetworkConnectionStatus());
+    }
+
+    @Test
+    public void getKnownNetworkConnectionStatus_remoteException_shouldReturnNull()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).getKnownNetworkConnectionStatus();
+
+        assertNull(manager.getKnownNetworkConnectionStatus());
+    }
+
+    @Test
+    public void getKnownNetworkConnectionStatus_serviceConnected_shouldReturnStatus()
+            throws RemoteException {
+        SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
+        KnownNetworkConnectionStatus status = new KnownNetworkConnectionStatus.Builder()
+                .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED)
+                .setExtras(new Bundle()).build();
+        manager.setService(mService);
+        when(mService.getKnownNetworkConnectionStatus()).thenReturn(status);
+
+        assertEquals(status, manager.getKnownNetworkConnectionStatus());
+    }
+
     private void setResources(@Mock Context context) {
         when(context.getResources()).thenReturn(mResources);
         when(mResources.getString(anyInt()))
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index d7f7fea..a04526a 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -16,14 +16,30 @@
 
 package android.net.wifi.sharedconnectivity.service;
 
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET;
+import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
+import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED;
+import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
+import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.Intent;
+import android.net.wifi.sharedconnectivity.app.DeviceInfo;
 import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
 import android.net.wifi.sharedconnectivity.app.TetherNetwork;
+import android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus;
+import android.os.Bundle;
 import android.os.Looper;
+import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -33,11 +49,37 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 /**
  * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}.
  */
 @SmallTest
 public class SharedConnectivityServiceTest {
+    private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
+    private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
+            .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+            .setConnectionStrength(2).setBatteryPercentage(50).build();
+    private static final TetherNetwork TETHER_NETWORK =
+            new TetherNetwork.Builder().setDeviceId(1).setDeviceInfo(DEVICE_INFO)
+                    .setNetworkType(NETWORK_TYPE_CELLULAR).setNetworkName("TEST_NETWORK")
+                    .setHotspotSsid("TEST_SSID").setHotspotBssid("TEST_BSSID")
+                    .setHotspotSecurityTypes(SECURITY_TYPES).build();
+    private static final List<TetherNetwork> TETHER_NETWORKS = List.of(TETHER_NETWORK);
+    private static final KnownNetwork KNOWN_NETWORK =
+            new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE_NEARBY_SELF)
+                    .setSsid("TEST_SSID").setSecurityTypes(SECURITY_TYPES)
+                    .setDeviceInfo(DEVICE_INFO).build();
+    private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
+    private static final SharedConnectivitySettingsState SETTINGS_STATE =
+            new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
+                    .setExtras(Bundle.EMPTY).build();
+    private static final TetherNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS =
+            new TetherNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
+                    .setTetherNetwork(TETHER_NETWORK).setExtras(Bundle.EMPTY).build();
+    private static final KnownNetworkConnectionStatus KNOWN_NETWORK_CONNECTION_STATUS =
+            new KnownNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_SAVED)
+                    .setKnownNetwork(KNOWN_NETWORK).setExtras(Bundle.EMPTY).build();
 
     @Mock
     Context mContext;
@@ -66,20 +108,60 @@
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
     }
 
-    /**
-     * Verifies service returns
-     */
     @Test
-    public void testOnBind() {
+    public void onBind_isNotNull() {
         SharedConnectivityService service = createService();
         assertNotNull(service.onBind(new Intent()));
     }
 
     @Test
-    public void testCallbacks() {
+    public void getTetherNetworks() throws RemoteException {
         SharedConnectivityService service = createService();
         ISharedConnectivityService.Stub binder =
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        service.setTetherNetworks(TETHER_NETWORKS);
+        assertArrayEquals(TETHER_NETWORKS.toArray(), binder.getTetherNetworks().toArray());
+    }
+
+    @Test
+    public void getKnownNetworks() throws RemoteException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        service.setKnownNetworks(KNOWN_NETWORKS);
+        assertArrayEquals(KNOWN_NETWORKS.toArray(), binder.getKnownNetworks().toArray());
+    }
+
+    @Test
+    public void getSharedConnectivitySettingsState() throws RemoteException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        service.setSettingsState(SETTINGS_STATE);
+        assertEquals(SETTINGS_STATE, binder.getSettingsState());
+    }
+
+    @Test
+    public void updateTetherNetworkConnectionStatus() throws RemoteException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        service.updateTetherNetworkConnectionStatus(TETHER_NETWORK_CONNECTION_STATUS);
+        assertEquals(TETHER_NETWORK_CONNECTION_STATUS, binder.getTetherNetworkConnectionStatus());
+    }
+
+    @Test
+    public void updateKnownNetworkConnectionStatus() throws RemoteException {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+        service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+        assertEquals(KNOWN_NETWORK_CONNECTION_STATUS, binder.getKnownNetworkConnectionStatus());
     }
 
     private SharedConnectivityService createService() {